云厂商的CVM好贵啊!除了丐版引流招新人入坑,其它稍高点的配置那价格就像在考验智商。作为小小个人如果想搭建一套比较完善的kubernetes集群的话,那资源就捉襟见肘了。如何更经济实惠的应对资源不够这个问题,本文给一点新思路。
基本原理
我曾经基于云上一些CVM搭建了一套k8s集群,简单玩玩尚可。但是如果想搞点花样,比如想加点监控或分析,Prometheus一上,内存就咔咔告警;日志采集分析一下?ES或loki等一上,存储也就喵喵在叫。这阻碍我们学习知识了呢?这不能忍。
看过我之前文章的朋友可能知道,我家里有不少小型计算单元与存储,NUC等小主机几台、NAS几台。这有算力、内存、存储都充足。那么,咱可以优势互补,搭建一个云上云下混合的k8s集群吗?当然可以。
本文将借助tailscale及其相关工具(有比较详细的全套使用指南)构建一个云与家庭一体的混合集群,并且在之上我们使用更轻量的k3s发行版搭建一个k8s集群。这样,我们就可借用云上主机,获得如公网IP,备案等必要的便利,同时需要计算资源时又依托于自己家庭内的低成本CPU内存及存储等,真正物尽其用。接下来和我一起来操作并且验证一下我们的想法吧!
构建网络
不知道你听过WireGuard没有,我在之前一篇文章有过介绍。作为已经被Linux内核集成的一个VPN,它使用上还稍有不便。但我们借助于一款名叫tailscale
的软件可快速构建安全可靠的虚拟局域网(VPN)。以下是GPT提供的介绍:
Tailscale是一款基于WireGuard协议开发的虚拟专用网络(VPN)工具,它允许用户将能够访问外网的设备聚合成一个虚拟的局域网,实现设备之间的安全通信。以下是关于Tailscale的详细介绍:
内网穿透:Tailscale可以将不同地点的设备连接起来,实现内网穿透,使得用户可以像在同一个局域网内一样访问这些设备。
设备连接:支持Windows、macOS、Linux、iOS和Android等多个平台,使得不同平台的设备可以轻松连接。
安全性:使用加密协议保护通信内容,确保数据的安全性。
它有强大的自动内网穿透能力,并且即使不能直连也会自动使用中继。据我观察,经常是使用几次中继收发消息后,自动打洞成功走上直连。你要说这么强大的软件,它有什么缺点不?那当然有:
- tailscale不是免费的,免费有接入节点限制,还有些高级功能有限。
- 中继都在国外,若需要经过它转发性能会有影响。
但是,别慌,这都能很好的解决。有个开源的控制面headscale,我们可以自建它解决官方各种限制。我们还可以自建中继服务DEPER。下面会一一道来,不过为了保证数据安全,首先我们得准备一个SSL证书。
使用ACME.sh自动申请证书
这里假定你已经有一个自己的域名了,我们可以借助于开源的acme.sh快速帮我们申请一个时长3个月的SSL证书,并且可以配置自动续期。这个证书是为了后面headscale服务安全性而用的,它也内置了自动申请证书功能,但目前(2024-10)只支持HTTP认证,这对国内域名不友好。我发现我在腾讯云上使用时不成功,可能是备案相关要求导致。所以下面我们就自己手动通过DNS认证来申请吧。
1
2
3
4
5
6
7
8
9
10
11
| # 安装acme.sh
curl https://get.acme.sh | sh -s email=your-email-address@qq.com
# 方案一:http验证:以standalone模式获取证书(如果网站未备案,在国内机器上操作可能报错,国外机器这里或能成功)
acme.sh --issue --standalone -d example-domain.com
# 方案二:以DNS验证。 我域名托管在cloudflare中,那么就需要设置环境变量。
export CF_Token="你在cloudflare的API token"
export CF_Email="你域名的Email"
acme.sh --issue --dns dns_cf -d example-domain.com
|
如果你想要调试一下,可以先使用测试域名,避免因为一些报错被平台限制申请。这相关信息查看这里。
申请成功后,你将会获得证书相关的文件,把它拷贝出来放到了/home/ubuntu/example-domain.com
备用。
搭建tailscale的控制面headscale
安装配置headscale
An open source, self-hosted implementation of the Tailscale control server.
多亏了开源的方案headscale,我们可以免费使用tailscale。查看这里的指引可轻松搭建。上面有容器方案使用起来简单,比如我们基于docker-compose来部署:
1
2
3
4
5
6
7
8
9
10
11
12
| version: "3.7"
services:
headscale:
image: headscale/headscale:v0.23
restart: unless-stopped
container_name: headscale
network_mode: host
volumes:
- /home/ubuntu/headscale/config:/etc/headscale
- /home/ubuntu/example-domain.com:/certs/
command: serve
|
上面为了简化网络,使用了host模式。重点是这里面的config配置怎么搞?你可以参考官方仓库中的示例稍作修改,主要是一些地址端口等小小变化,限于文章篇幅,以下仅diff出我的修改与原版差异:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| ubuntu@mercedes:~/headscale/config$ diff config.yaml config.yaml.ori
13,14c13
< #server_url: http://127.0.0.1:8080
< server_url: "https://example-domain.com:8812"
---
> server_url: http://127.0.0.1:8080
19,20c18,19
< listen_addr: 0.0.0.0:8812
< #listen_addr: 127.0.0.1:8080
---
> # listen_addr: 0.0.0.0:8080
> listen_addr: 127.0.0.1:8080
80c79
< enabled: true
---
> enabled: false
221,222c220,221
< tls_cert_path: "/certs/example-domain.com.crt"
< tls_key_path: "/certs/example-domain.com.key"
---
> tls_cert_path: ""
> tls_key_path: ""
|
这样我们将配置及前面的SSL证书挂载到容器内供headscale使用。之后docker-compose up -d
即可启动,注意检查一下启动日志是否一切正常。
在节点安装tailscale并注册
以我使用的Ubuntu系统,我们可以通过apt安装,其它系统官方也有很详细的安装指南。
1
2
3
4
5
| curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/jammy.noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/jammy.tailscale-keyring.list | sudo tee /etc/apt/sources.list.d/tailscale.list
sudo apt-get update
sudo apt-get install tailscale
|
安装完毕后可以输入tailscale version
查看一下版本。注意如果太老旧的版本和上面的headscale可能不太兼容哦,用当前最新一般没啥问题。
然后我们将node注册到headscale中:
1
| sudo tailscale up --login-server=https://example-domain.com:8812 --accept-routes=true --accept-dns=false
|
正常会收到一个链接。
1
2
3
| To authenticate, visit:
https://example-domain.com:8812/register/mkey:316fabaca478e6d35190bb8e331004b33c0f8c52c98f202105af41465014a269
|
打开页面,根据指示在headscale中注册,在注册前,我们可能要先创建一个namespace(新版本叫user)了。
1
2
3
4
5
| # 创建一个namespace(user),名叫union。别以为我写错了headscale,前一个是容器名,后一个是命令名
docker exec -it headscale headscale users create union
# 注册
headscale nodes register --user union --key mkey:316fabaca478e6d35190bb8e331004b33c0f8c52c98f202105af41465014a269
|
然后查看有如下结果:
1
2
| # 查看节点列表
docker exec -it headscale headscale nodes list
|
可能会输出类似的信息,能看到自己对应主机online了就注册成功了。
开启或搭建中继服务DERP
这一步可选,因为tailscale在全球提供了不少节点。但可惜没有大陆内的,延迟可能会有一定影响。如果你希望更快,我这里给一个我的搭建命令,一行docker命令即可(注意它也同样使用了上面申请的SSL证书)。
1
2
3
4
5
6
7
8
9
| docker run --restart always \
--name derper-with-verify-2024-10-15 \
--network host \
-v /home/ubuntu/example-domain.com/:/app/certs \
-v /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock \
-e DERP_CERT_MODE=manual \
-e DERP_ADDR=:12345 \
-e DERP_DOMAIN=example-domain.com \
fredliang/derper:latest
|
这个搭建好后,如果想让headscale使用我们自己搭建的DERP服务,则需要修改一下config。在DERP服务那一块:
1
2
| paths:
- /etc/headscale/derp.yaml
|
同时把这个文件挂载到容器内。这个文件的示例官方仓库也有,我的是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # /etc/headscale/derp.yaml
regions:
900:
regionid: 900
regioncode: gz-mercedes
regionname: Tencent Guangzhou
nodes:
- name: 900a
regionid: 900
hostname: example-domain.com
stunport: 3478
stunonly: false
derpport: 12345
|
修改完上面headscale相关配置并重启后,我们可以在节点上执行tailscale netcheck
看到定义的中继服务器信息。
加入其它节点并验证网络
除了云上我们购买的节点外,现在我们加入自己家里的一些虚拟机进来,让它未来成为混合集群的一部分。比如我想把家里一台Ubuntu24.04TLS作为节点一部分。这块操作和前面完全一致,先安装tailscale再注册到headscale,然后我们使用node list
查看:
1
2
3
4
| ubuntu@mercedes:~/headscale$ docker exec -it headscale headscale nodes list
ID | Hostname | Name | MachineKey | NodeKey | User | IP addresses | Ephemeral | Last seen | Expiration | Connected | Expired
1 | union-test | union-test | [j1D6O] | [aDlUn] | union | 100.64.0.3, fd7a:115c:a1e0::3 | false | 2024-10-22 06:46:41 | 0001-01-01 00:00:00 | online | no
2 | outside | outside | [TWC9a] | [MVLAj] | union | 100.64.0.4, fd7a:115c:a1e0::4 | false | 2024-10-22 06:46:40 | 0001-01-01 00:00:00 | online | no
|
如果你按上面部署了自己的DERP,相信速度会快一些。
1
2
3
4
5
6
| $ tailscale ping 100.64.0.3
pong from union-test (100.64.0.3) via DERP(gz-mercedes) in 18ms
pong from union-test (100.64.0.3) via DERP(gz-mercedes) in 19ms
pong from union-test (100.64.0.3) via DERP(gz-mercedes) in 19ms
pong from union-test (100.64.0.3) via DERP(gz-mercedes) in 18ms
pong from union-test (100.64.0.3) via DERP(gz-mercedes) in 19ms
|
也可以使用tailscale status
等检查网络状态。
到这里,我们的混合集群的网络部分已经Ready了,现在你可以看到这两台原本互不相干的机器,它们成为了一个局域网,并且是有着当前最安全的加密通信支撑的安全网络。接下来我们继续完成我们的集群部署以及功能验证。
补充题外话:tailscale好处不止于此,作为VPN,借助于它的Router能力,可以让你很方便的从外界安全的访问整个家庭网络,打通一切网络隔阂。鉴于此事于主题无关,就不在此多言啦。
部署集群
经过前文折腾,网络已经就绪。我已经迫不及待想上个集群了,虽然我曾经尝试过部署一个原生的kubernetes集群,但对于咱们小型集群,使用原生的Kubernetes占用资源还是太多了,我更推荐的还是k3s发行版。 我之前文章也提到过如何创建一个k3s集群。下面简单说明一下过程,并且增补一些实用技巧。
master节点
考虑到集群的稳定性,家里或许没有云上持续可靠,所以master节点可以考虑放在云上。但如果这个集群打算跑很久,云上VM你可能不续费的话,那么以家中节点为master也是可行的,要注意备份集群控制面数据即可。
1
2
3
4
5
6
7
8
9
| export INSTALL_K3S_CHANNEL=v1.26
export K3S_TOKEN=union-k3s-on-2024-10-23
curl -sfL https://get.k3s.io | sh -s - server \
--disable=traefik \
--node-ip=100.64.0.4 \
--node-external-ip=<机器外网IP> \
--flannel-iface=tailscale0 \
--tls-san=<机器外网 >
|
我们在上面的outside(香港)机器上执行如上命令,即可部署一个k8s 1.26版本。对几个参数作个解释:
- 关闭了默认的traefik。这是我的使用经验,因为未来我们使用它还需要再定义一些参数等(如自动申请证书啥的),故自行管理和部署更好。
- 节点IP使用的是我们tailscale分配的局域网地址。这样集群内部通信才能走咱们上面的混合网络。
- node-external-ip指定外网IP,用于LB等暴露时绑定的IP。
- flannel-iface确保每个节点都使用其 Tailscale接口,不然底层flannel网络不知道应该基于这个设备转发。
- tls-san需要指定机器的外网IP。为了你的k3s集群api未来可以从外部访问(kubeconfig)。
如果你安装时遇到网络问题进展缓慢,那么可以看下面node的部署,选择国内加速的镜像脚本即可。
你说都2024年10月了,还在用1.26版本?虽然我也尝试了较新的1.30版本,遇到了比较神奇的连接kubernetes api失败的报错,coredns一直起不来。
[INFO] plugin/ready: Still waiting on: “kubernetes”
[INFO] plugin/ready: Still waiting on: “kubernetes”
[INFO] plugin/ready: Still waiting on: “kubernetes”
有issue提可能和内核约莫有些关联,但是咱用的是Ubuntu24.04了,内核不应该低呀。也有issue提到tls-san添加上kubernetes svc ip,尝试均无效。想着换个版本呗?回退到1.26这个我常年用的老版本后,一切正常。此事颇为蹊跷,待我搞完这篇文章再来诊断诊断,在此记下来避免你也同样遇到。
node节点
我把master选择了云上机器,那么node我就选择加入自己家内部的虚拟机啦。我们部署一个node节点,在这里有意切换到一个国内安装脚本。
1
2
3
4
5
6
| export INSTALL_K3S_CHANNEL=v1.26
export K3S_TOKEN=union-k3s-on-2024-10-23
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh -s - agent --server https://100.64.0.4:6443 \
--flannel-iface=tailscale0 \
--node-ip=100.64.0.3
|
启动成功后,我们就有了一个可以小小集群了。查看一下:
1
2
3
4
| $ sudo k3s kubectl get nodes
NAME STATUS ROLES AGE VERSION
union-test Ready <none> 73s v1.30.5+k3s1
outside Ready control-plane,master 12h v1.30.5+k3s1
|
实用技巧
如果你遇到过镜像拉取很慢或者就完全拉不下来,可以为k3s server或agent配置一下代理,根据是master还是node情况来修改/etc/systemd/system/k3s.service.env
或/etc/systemd/system/k3s-agent.service.env
文件,在末尾添加上https代理即可。比如
1
2
| K3S_TOKEN='union-k3s-on-2024-10-23'
HTTPS_PROXY=http://<ip>:<port>
|
然后重启k3s/k3s-agent服务即可。
成果验收
如果上面都操作OK了,集群也调试就绪了。这么辛苦折腾一番,那么是时候收点好处了。我们搞个简单的,部署一个网站,网站实际托管在家里VM上,但是流量入口自云主机。你要问为什么这么搞?那是因为家宽都不让开放80/443端口呀,你得有云上主机才可以。
我们直接部署个nginx来演示,注意这里主动把它调度到家庭VM上(union-test节点)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
nodeName: union-test # 这里为了测试故意将它调度到家庭内部设备上
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
# k3s默认的servicelb提供了基于VM的LB能力,免费!
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
|
我们借助k3s中内置了servicelb,它通过iptables一系列规则,帮助你在无需使用云厂商的LB而达到对外暴露服务的目的。然后我们即可以直接访问云上主机的IP地址和端口即可实际访问到家里的计算资源。
有这个基础,未来可想象的空间就比较大啦。例如将数据库部署在家庭 NAS 上,将机器学习模型训练任务放到家庭闲置 GPU 上等。咱家里大House不一定非得装小姐姐,也可以放点有用的数据嘛:)
后记
其实k3s的较新版本也直接支持集成tailscale了,并且也支持我们自己部署的headscale作为控制面,详情查看上面链接。不过我们一步步设置过来,对全过程更清晰明白啦!
如此折腾之后,便可充分的利用自建资源,再也不担心搞点研究碰上资源不够的窘境了。未来或许你只需要在云上留下一台最普通的机器即可,连啥负载都可以不放,想想可不要太香,哗哗的省钱。
本篇文章就写到这里,感谢阅读。如果期待看到更多玩法,欢迎点赞鼓励,您的支持是我写作的动力!
我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号: