众所周知(并不),我有一台群晖 DS918+,上面用 Docker 跑了很多服务(比如馒头地球的服务端,还有一些自己写的家庭自动化的东西)。年前突发奇想要把它们迁到树莓派上,并且用 Kubernetes(简称 k8s)管理起来。整个过程我记录了一些笔记,这里整理出来。
为什么要用 k8s
我的现状:我的所有服务使用 docker-compose 进行编排。其中通过 Traefik 将不同域名反代到不同服务上。这里就产生了一些问题:
- 我需要通过一个 git 仓库来管理我这个
docker-compose.yml
和其它一些配置文件 - 每次增减服务都需要手动编辑这个
docker-compose.yml
,提交;然后 SSH 到服务器上,拉取,然后docker-compose up -d
- 我手上还有几台机器,但是它们彼此互相独立。我需要记住我的每一个服务都运行在拿台服务器,并且手动维护域名解析
这种情况下,k8s 就是一个万能的解决方案。
一些概念
k8s 把所有概念都抽象成了「资源」。对集群的所有操作也抽象成了对这些资源的增删查改。常用的几个资源类型可以简化理解如下:
- Node:节点,可以是一台云主机,一台树莓派,一部电脑。上面运行了很多 Pod
- Pod:一个基本的业务单位(一时没想到咋解释)。一个镜像跑起来就是一个 Pod(当然还有其它一些辅助性的东西在里面)。通常不需要手动创建
- Deployment:部署。集群会根据这个资源的定义拉取其中指定的镜像,并创建对应的 Pod,为 Pod 赋予相应的 label 以及其它元数据
- Service:服务。用于定义由哪一些具备对应 label 的 Pod 来承载这个 Service。同时也定义了这个服务会对外提供哪些端口
- Ingress:用于定义域名与 Service 的关系。外面的请求进来,集群的 Ingress Controller(比如 Nginx 或 Traefik)根据请求的域名对应的 Ingress 资源取得所指向的 Service,再通过 Service 定义的 label 筛选出对应的 Pod,并且将请求反代到其中的某个 Pod 上
系统安装
在树莓派上跑完整的 Kubernetes 是不太可行的。所以我选用了一个轻量级实现:k3s。硬件使用的是 8GB RAM 版本的树莓派 4B。
要运行 k3s,首先你需要 64 位版本的 Raspberry Pi OS。官网首页看到的是 32 位的,64 位需要通过链接下载:https://downloads.raspberrypi.org/raspios_lite_arm64/images/
下载完的镜像通过 balenaEtcher 之类的工具写到 TF 卡上,并且还编辑 cmdline.txt 开启 cgroups。在 cmdline.txt 末尾加上:
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
保存,退盘,插卡,上电。
更新软件
因为众所周知的原因,树莓派的 APT 源在国内访问速度不太理想。可以切换为 USTC 镜像。这里有一点注意的是,64 位版的 Rasp OS 使用的直接就是 Debian 的官方源,而不是 32 位的 Raspbian 源。
sudo sed -i 's|deb.debian.org|mirrors.ustc.edu.cn|g' /etc/apt/sources.list && \
sudo sed -i 's|security.debian.org/debian-security|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list && \
sudo sed -i 's|//archive.raspberrypi.org|//mirrors.ustc.edu.cn/archive.raspberrypi.org|g' /etc/apt/sources.list.d/raspi.list && \
sudo apt update && \
sudo apt upgrade
部署 master 节点
非常简单:
curl -sfL https://get.k3s.io | sh -
远程操作集群
本机安装了 kubectl
之后,通过配置,可以直接从本机通过 kubectl 命令操作集群,而不需要每次都 SSH 到 master 节点上。
注意,向公网暴露集群 API 是有一定危险性的。通常我的做法是只开放必要的端口,而本机操作服务器通过 OpenVPN 进行。当然,这篇文章假设你的树莓派放在家里的内网,也就不用关心这个。
在 master 节点上复制 kube config:
sudo cat /etc/rancher/k3s/k3s.yaml
将上面的内容复制到本地的 ~/.kube/config
。其中的 server
字段改为你的树莓派的地址。
部署 worker 节点
其实后来我发现我的业务就算单个 master 节点也完全够用…但既然记下了,也发出来,万一你们需要呢。
在 master 节点上执行这个命令,并记下 token:
sudo cat /var/lib/rancher/k3s/server/node-token
在 worker 节点上执行:
curl -sfL https://get.k3s.io | K3S_URL=https://master节点的地址:6443 K3S_TOKEN=刚刚的token sh -
检查运行情况:
sudo systemctl status k3s-agent.service
sudo journalctl -b -f -u k3s-agent.service
安装 cert-manager
cert-manager 用于管理 Let's Encrypt 证书的自动签发。这样对外就能提供 HTTPS 服务,且整个过程都可以自动完成、免维护。
参考:https://cert-manager.io/docs/installation/helm/
参考:https://artifacthub.io/packages/helm/cert-manager/cert-manager#configuration
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--version v1.7.0 \
--set installCRDs=true
配置 DNS-01
Let's Encrypt 需要定期进行证书更新(ACME)。默认的做法是走 HTTP-01 认证,也就是开放一个 HTTP 接口让 Let's Encrypt 服务访问你来完成核验。但我们的服务器跑在内网,而且我也不想开端口。因此有另一个选择是 DNS-01。
DNS-01 的原理简单来说,就是通过域名提供商的 API 创建一条 DNS TXT 记录携带必要的信息,让 Let's Encrypt 的服务器去解析这条记录取得信息,完成核验。我的域名解析是放在 Cloudflare 上的,对这个流程支持非常友好。
参考:https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/
在 https://dash.cloudflare.com/profile/api-tokens 创建一个 API token。
本地创建下面两个 yml 文件,并且通过 kubectl apply -f xxxx.yml
将它们提交到 k8s 集群。
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token-secret
namespace: cert-manager
type: Opaque
stringData:
api-token: 刚刚申请的 API token
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: 你的Email地址
privateKeySecretRef:
name: letsencrypt
solvers:
- dns01:
cloudflare:
email: 你的CF账号Email地址
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token
安装 Rancher
Rancher 是一个 k8s 管理面板。
参考:https://rancher.com/docs/rancher/v2.6/en/installation/install-rancher-on-k8s/
参考:https://rancher.com/docs/rancher/v2.6/en/installation/install-rancher-on-k8s/chart-options/#common-options
参考:rancher/rancher#26850
为了方便使用,我给它分配了一个二级域名。域名地址不一定(也不建议)用公网 API,用内网的就可以。
建一个 Certificate,这样 cert-manager 就会自动通过 Let's Encrypt 给你这个二级域名申请 HTTPS 证书:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: tls-rancher-ingress
namespace: cattle-system
spec:
secretName: tls-rancher-ingress
commonName: 你的rancher二级域名
dnsNames:
- 你的rancher二级域名
issuerRef:
name: letsencrypt
kind: ClusterIssuer
加一条 Ingress,用以将这个域名指向到 Rancher 服务:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rancher
namespace: cattle-system
spec:
rules:
- host: 你的rancher二级域名
http:
paths:
- backend:
service:
name: rancher
port:
number: 80
pathType: ImplementationSpecific
tls:
- hosts:
- 你的rancher二级域名
secretName: tls-rancher-ingress
安装 Rancher:
helm repo add rancher-latest https://releases.rancher.com/server-charts/latest
helm install rancher rancher-latest/rancher \
--namespace cattle-system --create-namespace \
--set hostname=你的rancher二级域名 \
--set bootstrapPassword=admin \
--set ingress.tls.source=secret
DevOps
留个坑…