众所周知(并不),我有一台群晖 DS918+,上面用 Docker 跑了很多服务(比如馒头地球的服务端,还有一些自己写的家庭自动化的东西)。年前突发奇想要把它们迁到树莓派上,并且用 Kubernetes(简称 k8s)管理起来。整个过程我记录了一些笔记,这里整理出来。

为什么要用 k8s

我的现状:我的所有服务使用 docker-compose 进行编排。其中通过 Traefik 将不同域名反代到不同服务上。这里就产生了一些问题:

  1. 我需要通过一个 git 仓库来管理我这个 docker-compose.yml 和其它一些配置文件
  2. 每次增减服务都需要手动编辑这个 docker-compose.yml,提交;然后 SSH 到服务器上,拉取,然后 docker-compose up -d
  3. 我手上还有几台机器,但是它们彼此互相独立。我需要记住我的每一个服务都运行在拿台服务器,并且手动维护域名解析

这种情况下,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 节点

参考:https://rancher.com/docs/k3s/latest/en/quick-start/

非常简单:

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

留个坑…