diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 0000000..e4cbbf7 --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Yu Sun"] +language = "en" +multilingual = false +src = "src" +title = "EdgeManager开发指北" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 0000000..ba304b9 --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,15 @@ +# Summary + +[项目简介](introduction.md) + +[系统配置](system.md) + +# 测试 + +- [集群搭建](tests/cluster.md) +- [运行多节点TimescaleDB服务](tests/tsdb.md) +- [技术细节](tests/details.md) +- [常见问题](tests/questions/questions.md) + - [Pods无法清除](tests/questions/terminating.md) + - [Pods创建失败](tests/questions/failed_pod.md) + - [使用物理内存作持久化存储](tests/questions/ram.md) diff --git a/docs/src/introduction.md b/docs/src/introduction.md new file mode 100644 index 0000000..48b6dc5 --- /dev/null +++ b/docs/src/introduction.md @@ -0,0 +1,3 @@ +# 项目简介 + +待补充,请阅读其他内容。 \ No newline at end of file diff --git a/docs/src/system.md b/docs/src/system.md new file mode 100644 index 0000000..b83f638 --- /dev/null +++ b/docs/src/system.md @@ -0,0 +1,43 @@ +# 系统配置 + +> 建议在开发环境、测试环境和生产环境均应用该页面中建议的设置。 +> +> 如果使用集群,则每台机器都应应用该页面中建议的设置。 + +## OS安装 + +关键参数: + +1. Ubuntu Server 22.04.1 LTS +2. minimized安装 +3. 磁盘挂载不要选LVM方式 +4. 关闭swap(`/etc/fstab`去掉对应行后重启生效) + +## 更改时区为一致 + +```bash +sudo timedatectl set-timezone Asia/Shanghai +``` + +## 内核调优 + +运行以下命令: + +```bash +echo ' +net.ipv4.tcp_max_tw_buckets = 20000 +net.core.somaxconn = 65535 +net.ipv4.tcp_max_syn_backlog = 262144 +net.core.netdev_max_backlog = 30000 +fs.file-max = 6815744 +net.netfilter.nf_conntrack_max = 2621440 +' | sudo tee -a /etc/sysctl.conf +echo ' +* soft nofile 1024000 +* hard nofile 1024000 +root soft nofile 1024000 +root hard nofile 1024000 +' | sudo tee -a /etc/security/limits.conf +``` + +然后`sudo reboot`重启服务器。 diff --git a/docs/src/tests/cluster.md b/docs/src/tests/cluster.md new file mode 100644 index 0000000..2edc93a --- /dev/null +++ b/docs/src/tests/cluster.md @@ -0,0 +1,145 @@ +# 集群搭建 + +> 操作系统的安装和配置请参照[系统配置](../system.md)章节。 + +## hostname配置 + +可通过`hostname`命令检查当前服务器的hostname。 + +在一切开始之前,首先要确保4台物理机的hostname分别为:`an`、`dn0`、`dn1`和`dn2`。 + +其中,`an`代表多节点TimescaleDB集群中的Access Node(接入节点),`dnx`则代表Data Node(数据节点)。 + +在安装操作系统时,可以一并设置hostname。如果有改变hostname的需要,可以类似这样操作: + +```bash +sudo hostnamectl set-hostname an +``` + +则hostname会被改为`an`(**即时生效**)。 + +## hosts配置 + +可通过编辑`/etc/hosts`文件,将局域网内hostname和IP地址的映射关系写入,此处不赘述具体步骤。 + +> 需确保集群内每一节点都可以**正确地**将其他节点的hostname解析为对应的IP地址。 + +## MicroK8s安装 + +```bash +sudo snap install microk8s --classic --channel=1.25 +``` + +> MicroK8s安装也可以在Ubuntu安装过程勾选完成。 +> +> 1.25版本对最新内核的cgroup v2识别可能有问题(`microk8s.inspect`会[提示warning](https://github.com/canonical/microk8s/issues/3375)),忽略即可,并不影响集群稳定性。 + +将当前用户加入`microk8s`用户组方便管理: + +```bash +sudo usermod -a -G microk8s $USER +sudo chown -f -R $USER ~/.kube +newgrp microk8s +``` + +gcr镜像无法在祖国大陆直接获取,[使用阿里云替代](https://microk8s.io/docs/registry-private#configure-registry-mirrors-7),但截至该文档编写时,官方文档提供的解决方法[有问题](https://github.com/canonical/microk8s/issues/472#issuecomment-1256947878),可以用下述方法替代: + +```bash +sed -i 's#k8s.gcr.io#registry.aliyuncs.com/google_containers#g' /var/snap/microk8s/current/args/containerd-template.toml +sudo snap restart microk8s +``` + +确认K8s是否已经正常运行: + +```bash +microk8s.status -w +``` + +如无异常,片刻后会显示如下内容: + +```pre +microk8s is running +high-availability: no + datastore master nodes: 127.0.0.1:19001 + datastore standby nodes: none +addons: + enabled: + ha-cluster # (core) Configure high availability on the current node + disabled: + community # (core) The community addons repository + dashboard # (core) The Kubernetes dashboard + dns # (core) CoreDNS + gpu # (core) Automatic enablement of Nvidia CUDA + helm # (core) Helm 2 - the package manager for Kubernetes + helm3 # (core) Helm 3 - Kubernetes package manager + host-access # (core) Allow Pods connecting to Host services smoothly + hostpath-storage # (core) Storage class; allocates storage from host directory + ingress # (core) Ingress controller for external access + mayastor # (core) OpenEBS MayaStor + metallb # (core) Loadbalancer for your Kubernetes cluster + metrics-server # (core) K8s Metrics Server for API access to service metrics + prometheus # (core) Prometheus operator for monitoring and logging + rbac # (core) Role-Based Access Control for authorisation + registry # (core) Private image registry exposed on localhost:32000 + storage # (core) Alias to hostpath-storage add-on, deprecated +``` + +此外,为了方便拉取`docker.io`上的镜像,最好配置使用[阿里云的镜像加速器](https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors): + +```bash +echo ' +server = "https://registry-1.docker.io" + +[host."https://r3vfmoy2.mirror.aliyuncs.com"] + capabilities = ["pull", "resolve"] +' | tee /var/snap/microk8s/current/args/certs.d/docker.io/hosts.toml +sudo snap restart microk8s +``` + +重复上述步骤使得MicroK8s在每台服务器上得以妥善安装配置。 + +## 组建集群 + +组建集群的详细步骤可以参考[官方文档](https://microk8s.io/docs/clustering),但需**注意**: + +1. `microk8s join`之前,需要在每个node的`/etc/hosts`中加入其他node的信息 +2. 每个节点加入后,token会过期,需要再次`microk8s add-node`获取新的token + +## 启用Helm3扩展 + +> Helm3扩展自MicroK8s v1.25开始已经默认开启,无需手动安装。 + +同样是祖国大陆受GFW限制的问题,如果直接`microk8s.enable helm3`是从官方拉取文件安装Helm3,速度极慢且中途易出现下载中断。 + +可以把对应的安装脚本里的文件位置指向华为云的镜像: + +```bash +sed 's#https://get.helm.sh#https://mirrors.huaweicloud.com/helm/v3.8.0#' /var/snap/microk8s/common/addons/core/addons/helm3/enable | SNAP=/snap/microk8s/current SNAP_DATA=/var/snap/microk8s/current SNAP_ARCH=amd64 bash +``` + +运行完毕后再执行`microk8s.status`即可看到Helm3扩展已启用。 + +## 后续配置 + +由于我们要使用Helm来作为包管理器,而helm会读取K8s的`config`文件,如果不加设置,默认会使用`/var/snap/microk8s/current/credentials/client.config`,但由于安全性问题,会被警告: + +```pre +WARNING: Kubernetes configuration file is group-readable. This is insecure. +``` + +所以我们需要创建用户级别的配置文件并赋予正确的文件权限: + +```bash +microk8s config > ~/.kube/config +chmod go-r ~/.kube/config +``` + +为便于操作,再将常用命令做一下`alias`: + +```bash +echo ' +alias kubectl="microk8s kubectl" +alias helm="microk8s helm3 --kubeconfig ~/.kube/config" +' | tee -a ~/.bash_aliases +``` + diff --git a/docs/src/tests/details.md b/docs/src/tests/details.md new file mode 100644 index 0000000..b0c9b35 --- /dev/null +++ b/docs/src/tests/details.md @@ -0,0 +1,89 @@ +# 技术细节 + +## 0. 如何确保集群的access-node/data-node pods运行于我们期望的物理节点上? + +首先要明确一个概念,由于需要对Access Node和Data Node进行区分,TimescaleDB集群其实是一个有状态的应用。 + +有状态应用借助K8s的[StatefulSet workload](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)可以很便捷的部署。 + +但很久以前TimescaleDB官方维护的multinodes版本Helm仓库里对于Access Node的节点选择并没有清晰定义。 + +[`charts/templates/statefulset-timescaledb-accessnode.yaml`](https://github.com/timescale/helm-charts/blob/29b589c96821a3f9ffe890cd775d7da471007aa0/charts/timescaledb-multinode/templates/statefulset-timescaledb-accessnode.yaml#L117-L118): + +```yaml +{{- with .Values.nodeSelector }} + nodeSelector: +``` + +[`charts/values.yaml`](https://github.com/timescale/helm-charts/blob/29b589c96821a3f9ffe890cd775d7da471007aa0/charts/timescaledb-multinode/values.yaml#L91): + +```yaml +nodeSelector: {} +``` + +咱就是说,直接就摆烂了。 + +这直接导致多个接入节点和数据节点的pods很大概率错误地尝试分配在同一个物理节点上,由于资源冲突,最终造成随机的某(几)个节点创建失败。 + +按照[我们之前对hostname的定义](cluster.md#hostname配置),最后将`charts/templates/statefulset-timescaledb-accessnode.yaml`里对应部分改为: + +```yaml + nodeSelector: + kubernetes.io/hostname: an +``` + +解决了这个问题。 + +`kubernetes.io/hostname`是K8s的一个保留label,通过如下命令可以查看: + +```bash +kubectl get no --show-labels=true +``` + +![](node_label.png) + +这里有一个复杂情况,如果设定的Data Node数目超过了节点数目,亦或是其中某个节点挂掉,集群的Access Node或者Data Node是会尝试漂移到某个节点上的,也就是某些节点上会启动多于一个TimescaleDB实例。这是我们不想看到的。 + +在Helm chart中妥善设置节点亲和性可以有效解决这个问题: + +`charts/templates/statefulset-timescaledb-accessnode.yaml`: + +```yaml +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - an +``` + +`charts/templates/statefulset-timescaledb-datanode.yaml`: + +```yaml +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: NotIn + values: + - an +``` + +核心思想是使用`requiredDuringSchedulingIgnoredDuringExecution`替代`preferredDuringSchedulingIgnoredDuringExecution`策略,以实现强制性的调度[^note]。 + +[^note]: + +## 1. TimescaleDB集群的多节点间操作是如何实现免密码认证的? + +PostgreSQL有一个[Password file](https://www.postgresql.org/docs/current/libpq-pgpass.html)的设定,通过一个`.pgpass`来进行一些免密认证。 + +TimescaleDB可以复用这个文件,但是必须在PostgreSQL的配置文件中以[`timescaledb.passfile`](https://docs.timescale.com/timescaledb/latest/how-to-guides/configuration/timescaledb-config/#timescaledb-passfile-string)字段的形式提供。 + +打个比方,应用层中使用[`create-distributed-hypertable()`](https://docs.timescale.com/api/latest/distributed-hypertables/create_distributed_hypertable/#create-distributed-hypertable)函数创建分布式超表的时候,其是不允许将密码传参提供的,这个时候就一定要环境中提供免密认证,使得各个nodes之间可以自由连通。 + +而[`add_data_node()`](https://docs.timescale.com/api/latest/distributed-hypertables/add_data_node/)函数则可以直接传参指定密码,因其为环境部署过程中可能需要的。但是出于安全性考虑,也**不建议**这样做。 diff --git a/docs/src/tests/get_all.png b/docs/src/tests/get_all.png new file mode 100644 index 0000000..59da0a8 Binary files /dev/null and b/docs/src/tests/get_all.png differ diff --git a/docs/src/tests/node_label.png b/docs/src/tests/node_label.png new file mode 100644 index 0000000..c64ea59 Binary files /dev/null and b/docs/src/tests/node_label.png differ diff --git a/docs/src/tests/psql.png b/docs/src/tests/psql.png new file mode 100644 index 0000000..83a86a5 Binary files /dev/null and b/docs/src/tests/psql.png differ diff --git a/docs/src/tests/questions/failed_pod.md b/docs/src/tests/questions/failed_pod.md new file mode 100644 index 0000000..0f5cc2f --- /dev/null +++ b/docs/src/tests/questions/failed_pod.md @@ -0,0 +1,33 @@ +# Pods创建失败 + +## 问题描述 + +使用`kubectl get all -l release=tsdb` 发现有单个数据节点或`(1个数据节点 + 1个接入节点)`的pods的状态是某种error,`kubectl describe`之后发现类似下面的报错: + +```pre +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Pulled 4m34s (x2 over 4m48s) kubelet Container image "timescale/timescaledb-ha:pg14.5-ts2.8.0-patroni-static-primary-latest" already present on machine + ... + Warning MissingClusterDNS 7s (x8 over 39s) kubelet pod: "tsdb-timescaledb-data-1_default(62ba2d9e-6cff-4446-aa2b-ae46f5948997)". kubelet does not have ClusterDNS IP configured and cannot create Pod using "ClusterFirst" policy. Falling back to "Default" policy. +``` + +## 解决方案 + +很容易误会是没有启用`core/dns`扩展所致,但事实上我们已经[启用了](../tsdb.md#安装必要扩展addons)改扩展。 + +`charts/templates/svc-timescaledb-data.yaml`文件中的相关定义为: + +```yaml +spec: + clusterIP: None +``` + +而K8s其实是允许定义Headless Services的[^note],就是通过将`.spec.clusterIP`指定为`"None"`。 + +所以问题其实是:**没有能够正确应用Selector把pod分配到正确的节点上**。 + +具体的设置方法可参照[技术细节](../details.md#0-如何确保集群的access-node-pod运行于我们设定的物理节点上)。 + +[^note]: \ No newline at end of file diff --git a/docs/src/tests/questions/questions.md b/docs/src/tests/questions/questions.md new file mode 100644 index 0000000..dd3707e --- /dev/null +++ b/docs/src/tests/questions/questions.md @@ -0,0 +1,3 @@ +# 常见问题 + +本章节用于记录测试过程中常见问题及解决方案。 \ No newline at end of file diff --git a/docs/src/tests/questions/ram.md b/docs/src/tests/questions/ram.md new file mode 100644 index 0000000..440429c --- /dev/null +++ b/docs/src/tests/questions/ram.md @@ -0,0 +1,38 @@ +# 使用物理内存作持久化存储 + +首先划分一块足够的内存空间并挂载: + +```bash +sudo mkdir -pm777 /mnt/ramdisk +sudo mount -t ramfs -o size=10G ramdisk /mnt/ramdisk +``` + +> **需要在每个节点上都如法炮制**。 + +创建新的StorageClass: + +```bash +kubectl apply -f ram-hostpath-sc.yaml +``` + +重新启动集群服务的时候,使用命令行传参的方式覆盖默认(`values.yaml`)设置。 + +```bash +helm install tsdb --set persistentVolume.size=8G --set persistentVolume.storageClass=ram-hostpath . +``` + +Microk8s的hostpath storage有bug,新创建的PV权限并非`777`,会导致StatefulSet的pods全部启动失败。暂时只能在每个节点上手动设置权限: + +```bash +sudo chmod -R 777 /mnt/ramdisk/ +``` + +之后静等对应的pods自动重启。 + +[移除集群](../tsdb.html#移除集群)的操作不再赘述。 + +如要清除创建的StorageClass: + +```bash +kubectl delete sc/ram-hostpath +``` \ No newline at end of file diff --git a/docs/src/tests/questions/sandbox.png b/docs/src/tests/questions/sandbox.png new file mode 100644 index 0000000..76edffa Binary files /dev/null and b/docs/src/tests/questions/sandbox.png differ diff --git a/docs/src/tests/questions/terminating.md b/docs/src/tests/questions/terminating.md new file mode 100644 index 0000000..c5f5de5 --- /dev/null +++ b/docs/src/tests/questions/terminating.md @@ -0,0 +1,27 @@ +# Pods无法清除 + +## 问题描述 + +执行`helm uninstall`或者`kubectl delete`等命令的时候发现pods无法删除: + +![](terminating.png) + +使用`kubectl describe`观察详情的时候发现通讯失败(但Sandbox其实已经被清除了): + +![](sandbox.png) + +## 解决方案 + +运行: + +```bash +for p in $(kubectl get pods | grep Terminating | awk '{print $1}'); do kubectl delete pod $p --grace-period=0 --force; done +``` + +如果运行上面的命令后仍有pod处于`Unknown`状态,大胆清除[^note]: + +```bash +kubectl patch pod -p '{"metadata":{"finalizers":null}}' +``` + +[^note]: \ No newline at end of file diff --git a/docs/src/tests/questions/terminating.png b/docs/src/tests/questions/terminating.png new file mode 100644 index 0000000..7c264ab Binary files /dev/null and b/docs/src/tests/questions/terminating.png differ diff --git a/docs/src/tests/show_nodes.png b/docs/src/tests/show_nodes.png new file mode 100644 index 0000000..017473f Binary files /dev/null and b/docs/src/tests/show_nodes.png differ diff --git a/docs/src/tests/timescaledb-multi.png b/docs/src/tests/timescaledb-multi.png new file mode 100644 index 0000000..35291a1 Binary files /dev/null and b/docs/src/tests/timescaledb-multi.png differ diff --git a/docs/src/tests/tsdb.md b/docs/src/tests/tsdb.md new file mode 100644 index 0000000..0def6e4 --- /dev/null +++ b/docs/src/tests/tsdb.md @@ -0,0 +1,115 @@ +# 运行多节点TimescaleDB服务 + +在[上一章节](cluster.md)中,我们完成了基本的K8s集群搭建并启用了Helm3扩展以便于利用helm charts便捷部署应用于其上。 + +接下来说明如何配置一个测试用集群。 + +> 注意,生产环境中的配置可以参考本章,但不应照搬; +> +> 本章中的配置仅提供**最简易**环境并论证运维**可行性**,性能和稳定性方面均**无法胜任生产环境**。 + +## 安装必要扩展(Addons) + +运行以下命令,并耐心等待自动配置完成: + +```bash +microk8s enable dns hostpath-storage metallb +``` + +`dns`是必需的,因为Data Nodes上运行的TimescaleDB实例作为[Headless Services](questions/failed_pod.md#解决方案),本身是没有为它们提供IP的。如果没有K8s内建的DNS服务,根本就无法根据pod name获取到具体的IP,也就是说这些pods间无法通讯,数据库集群会创建失败。 + +其中,`hostpath-storage`是microk8s自带的一个PV(**P**ersistent**V**olumes,持久化存储)插件,其为开发者提供了动态供应(dynamic provisioning)的PV。 + +简单来讲,用户通过helm charts或其他方式每创建一个带有PVC(**P**ersistent**V**olume**C**laim)的pod,动态供应者(dynamic provisioner)就会为其自动创建一个可用的PV。 + +> **禁止在生产环境中使用hostpath-storage!** +> +> 由hostpath-storage提供的PV并不支持数据在节点间迁移,所以只能用于开发/测试环境。 + +`metallb`则是轻量级的为bare-metal服务器上提供负载均衡的解决方案。 + +> bare-metal特指比如IoT场景这种局域网中互连的服务器们,即区别于“云上”的虚拟服务器(**V**irtual **P**rivate **S**erver, VPS)。 + +启用metallb扩展时,命令行会提示输入IP网段来创建一个地址池,当service类型为`LoadBalancer`时,metallb会从已创建的地址池中挑选一个IP来作为服务的`EXTERNAL-IP`。 + +> 应为metallb提供**与host同一层的**IP地址,因其可以直接在局域网内被直接访问,而无需额外的DNS服务。 +> +> 但**不应重复使用节点的IP**,会导致network traffic,造成节点间无法通讯,集群不能正常工作。 +> +> 这也为**部署于K8s集群外的应用**(比如EdgeManager)提供了访问集群内服务的支撑。 + +指定网段时可以使用`-`符号连接两个IP地址来表示一个区间。比如也可以使用这种方式在启用扩展时直接创建地址池: + +```bash +microk8s.enable metallb:192.168.0.198-192.168.0.199 +``` + +运行完毕后,`192.168.0.198`和`192.168.0.199`均会被加入地址池中。metallb一般会挑选第一个,当第一个地址不可用时则顺序替换为下一个地址。 + +## 启动集群 + +先拉取代码仓库: + +```bash +git pull http://118.195.187.246:10030/ysun/EdgeManager.git +``` + +运行: + +```bash +# 进入位于项目根目录的charts目录内 +cd EdgeManager/charts +helm install tsdb . +``` + +## 移除集群 + +运行: + +```bash +helm uninstall tsdb +``` + +但`helm uninstall`并不会丧心病狂地删除持久化的数据,因此如果想**彻底删除**这些数据,可以用以下命令删除PVC: + +```bash +kubectl delete $(kubectl get pvc -l release=tsdb -o name) +``` + +稍后对应的PV也会被清除(因为我们之前使用的PV插件`hostpath-storage`是dynamic provisioner)。 + +> 如果需要重新部署服务,建议使用`kubectl get pv`确定PV已经完全被释放后再进行。 + +## 验证集群状态 + +运行: + +```bash +kubectl get all -l release=tsdb +``` + +![](get_all.png) + +图中可见,`192.168.0.198`即为metallb提供的可供集群外部应用访问的IP。 + +验证Access Node上是否成功加入了数据节点: + +```bash +kubectl exec -ti $(kubectl get pod -l timescaleNodeType=access -o name) -c timescaledb -- psql -d scada -c 'select node_name from timescaledb_information.data_nodes' +``` + +![](show_nodes.png) + +尝试从另外一台物理机访问该多节点TimescaleDB集群: + +![](psql.png) + +至此,集群已经可以正常使用了。 + +## 部署图 + +该测试环境的部署结构[^note]为: + +![](timescaledb-multi.png) + +[^note]: