docs(book):增加技术细节、自定义sc、修复dns和版本号错误说明

This commit is contained in:
Yu Sun
2022-10-10 16:39:28 +08:00
parent 9fd20de60a
commit 2165d95937
18 changed files with 517 additions and 0 deletions

6
docs/book.toml Normal file
View File

@@ -0,0 +1,6 @@
[book]
authors = ["Yu Sun"]
language = "en"
multilingual = false
src = "src"
title = "EdgeManager开发指北"

15
docs/src/SUMMARY.md Normal file
View File

@@ -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)

3
docs/src/introduction.md Normal file
View File

@@ -0,0 +1,3 @@
# 项目简介
待补充,请阅读其他内容。

43
docs/src/system.md Normal file
View File

@@ -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`重启服务器。

145
docs/src/tests/cluster.md Normal file
View File

@@ -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
```

89
docs/src/tests/details.md Normal file
View File

@@ -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]: <https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/#schedule-a-Pod-using-required-node-affinity>
## 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/)函数则可以直接传参指定密码,因其为环境部署过程中可能需要的。但是出于安全性考虑,也**不建议**这样做。

BIN
docs/src/tests/get_all.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/src/tests/psql.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -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]: <https://kubernetes.io/docs/concepts/services-networking/service/#headless-services>

View File

@@ -0,0 +1,3 @@
# 常见问题
本章节用于记录测试过程中常见问题及解决方案。

View File

@@ -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
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -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 <pod> -p '{"metadata":{"finalizers":null}}'
```
[^note]: <https://kubernetes.io/docs/tasks/run-application/force-delete-stateful-set-pod/#delete-pods>

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

115
docs/src/tests/tsdb.md Normal file
View File

@@ -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]: <https://docs.timescale.com/timescaledb/latest/overview/timescale-kubernetes/#multi-node-distributed-timescaledb>