K8s 部署 TeslaMate

发布于 2025-02-11  539 次阅读


TeslaMate 是一个开源自托管的三方数据收集和展示的项目,专用于特斯拉的车辆。

官方提供了基于 Docker 和 Linux 的 安装手册,如果你选择使用 Docker 部署,那么直接按照官方文档部署即可。

这里改为使用 Kubernetes 部署,使用 Longhorn 作为持久性储存。

部署准备

  • Kubernetes: v1.29.3
  • StorageClass: longhorn、nfs-client
  • 域名 TLS Secret

创建一个单独的命名空间 teslamate

k create ns teslamate

如果在国内镜像无法拉取,可以自己先拉取然后推送到国内的例如阿里云等提供的镜像仓库里。

储存配置

这里使用 Longhorn 作为存储类,当然也可以指定 NFS 等其他储存。

根据官方 Docker 部署文档,一共要创建 5 个 PVC。

# TeslaMate
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: teslamate-import-pvc
  namespace: teslamate
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 5Gi
---
# PostgreSQL
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: teslamate-db-pvc
  namespace: teslamate
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 10Gi
---
# Grafana
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: teslamate-grafana-data-pvc
  namespace: teslamate
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 10Gi
---
# Mosquitto
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mosquitto-conf-pvc
  namespace: teslamate
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 5Gi
---
# Mosquitto
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mosquitto-data-pvc
  namespace: teslamate
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 5Gi

执行命令创建储存

k apply -f pvc.yaml

部署配置

PostgreSQL

使用 STS 部署,注意修改用户密码。

# postgresql.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: database
  namespace: teslamate
spec:
  serviceName: database 
  replicas: 1
  selector:
    matchLabels:
      app: database
  template:
    metadata:
      labels:
        app: database
    spec:
      # Longhorn 默认挂载的卷权限是 root 用户
      # 所以使用 initContainer 修改一下挂载的目录权限,否则会报错
      initContainers:
      - name: volume-permissions
        image: busybox:latest
        command: ["sh", "-c", "chown -R 1001:1001 /bitnami/postgresql"]
        securityContext:
          privileged: true
        volumeMounts:
        - mountPath: /bitnami/postgresql
          name: teslamate-db
      containers:
      - name: postgres
        env:
        - name: POSTGRES_USER
          value: "teslamate"
        - name: POSTGRES_PASSWORD
          value: "set_database_password"
        - name: POSTGRESQL_POSTGRES_PASSWORD # 指定下 postgres 用户的密码后面会用到
          value: "set_database_password"
        - name: POSTGRES_DB
          value: "teslamate"
        volumeMounts:
        - name: teslamate-db
          mountPath: /bitnami/postgresql
        image: bitnami/postgres:17.2.0-debian-12-r10
        imagePullPolicy: IfNotPresent
        livenessProbe:
          exec:
            command:
              - /bin/sh
              - '-c'
              - 'pg_isready -U postgres'
          failureThreshold: 3
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 5
        ports:
        - containerPort: 5432
          name: postgresql
          protocol: TCP
        resources:
          limits:
            cpu: '1'
            memory: 2Gi
          requests:
            cpu: 100m
            memory: 500Mi
      volumes:
      - name: teslamate-db
        persistentVolumeClaim:
          claimName: teslamate-db-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: database
  namespace: teslamate
spec:
  clusterIP: None  # Headless Service
  selector:
    app: database
  ports:
  - protocol: TCP
    port: 5432
    targetPort: 5432

Mosquitto

# mosquitto.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mosquitto
  namespace: teslamate
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mosquitto
  template:
    metadata:
      labels:
        app: mosquitto
    spec:
      containers:
      - name: mosquitto
        image: eclipse-mosquitto:2
        command: ["mosquitto", "-c", "/mosquitto-no-auth.conf"]
        volumeMounts:
        - name: mosquitto-conf
          mountPath: /mosquitto/config
        - name: mosquitto-data
          mountPath: /mosquitto/data
      volumes:
      - name: mosquitto-conf
        persistentVolumeClaim:
          claimName: mosquitto-conf-pvc
      - name: mosquitto-data
        persistentVolumeClaim:
          claimName: mosquitto-data-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: mosquitto
  namespace: teslamate
spec:
  selector:
    app: mosquitto
  ports:
  - protocol: TCP
    port: 1883
    targetPort: 1883

Grafana

# grafana.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
  namespace: teslamate
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      labels:
        app: grafana
    spec:
      # 使用 Root 运行否则会报错
      securityContext:
        runAsUser: 0
        runAsGroup: 0
        fsGroup: 0
      containers:
      - name: grafana
        image: teslamate/grafana:latest
        env:
        - name: DATABASE_USER
          value: "teslamate"
        - name: DATABASE_PASS # 设置成 POSTGRES_PASSWORD
          value: "set_database_password"
        - name: DATABASE_NAME
          value: "teslamate"
        - name: DATABASE_HOST
          value: "database"
        - name: GF_SECURITY_ADMIN_USER # Grafana 用户名
          value: "set_your_username"
        - name: GRAFANA_PASSWD # Grafana 密码
          value: "set_your_password"
        - name: GF_SECURITY_ADMIN_PASSWORD # Grafana 密码
          value: "set_your_password"
        - name: GF_AUTH_ANONYMOUS_ENABLED
          value: "false"
        - name: GF_SERVER_DOMAIN # 修改为你自己的域名
          value: "tesla.example.com"
        - name: GF_SERVER_ROOT_URL
          value: "%(protocol)s://%(domain)s/grafana"
        - name: GF_SERVER_SERVE_FROM_SUB_PATH # 使用二级目录运行 Grafana 要设置为 True
          value: "true"
        ports:
        - containerPort: 3000
        volumeMounts:
        - name: teslamate-grafana-data
          mountPath: /var/lib/grafana
      volumes:
      - name: teslamate-grafana-data
        persistentVolumeClaim:
          claimName: teslamate-grafana-data-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: grafana
  namespace: teslamate
spec:
  selector:
    app: grafana
  ports:
  - protocol: TCP
    port: 3000
    targetPort: 3000

TeslaMate

# teslamate.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: teslamate
  namespace: teslamate
spec:
  replicas: 1
  selector:
    matchLabels:
      app: teslamate
  template:
    metadata:
      labels:
        app: teslamate
    spec:
      containers:
      - name: teslamate
        image: teslamate/teslamate:latest
        securityContext:
          capabilities:
            drop:
            - ALL
        env:
        - name: ENCRYPTION_KEY # cat /dev/urandom| head -1 | md5sum | head -c 32 随机生成一个 32 位的密钥
          value: "random_secret_key"
        - name: DATABASE_USER
          value: "teslamate"
        - name: DATABASE_PASS # 设置成 POSTGRES_PASSWORD
          value: "set_database_password"
        - name: DATABASE_NAME
          value: "teslamate"
        - name: DATABASE_HOST
          value: "database"
        - name: MQTT_HOST
          value: "mosquitto"
        - name: VIRTUAL_HOST # 修改为你自己的域名
          value: "tesla.example.com"
        - name: CHECK_ORIGIN
          value: "true"
        - name: TZ # 设置时区
          value: "Asia/Shanghai"
        ports:
        - containerPort: 4000
        volumeMounts:
        - name: import
          mountPath: /opt/app/import
      volumes:
      - name: import
        persistentVolumeClaim:
          claimName: teslamate-import-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: teslamate
  namespace: teslamate
spec:
  selector:
    app: teslamate
  ports:
  - protocol: TCP
    port: 4000
    targetPort: 4000

启动服务

依次启动服务

k apply -f mosquitto.yaml
k apply -f grafana.yaml
k apply -f postgresql.yaml

数据库启动后需要先做一个操作,因为 bitnami 的 PostgreSQL 容器里创建的用户没有创建插件的权限,所以直接启动 TeslaMate 的话会报如下的错误。

ERROR 42501 (insufficient_privilege) permission denied to create extension "earthdistance

解决方法就是在 PostgreSQL 里手动给 teslamate 用户授予临时超级用户权限。

# 进入数据库容器里
k -n teslamate exec -it database-0 -c postgres -- bash
# 登录数据库,密码就是上面设置的 POSTGRESQL_POSTGRES_PASSWORD
psql -U postgres
# 赋予用户超级权限
alter user teslamate with superuser;
exit
# 等服务启动完成后,再进入数据库执行命令取消超级权限。
alter user teslamate with nosuperuser;

然后继续创建 TeslaMate 服务,等待容器就绪即可。

k apply -f teslamate.yaml

创建 Ingress 入口

因为 TeslaMate 并没有用户管理,配置了 API 和 Refresh API 就可以直接访问,所以如果暴露在公网,需要手动添加一层 Basic Auth 来保护服务。

USER=user
PASSWORD=password
echo "${USER}:$(openssl passwd -stdin -apr1 <<< ${PASSWORD})" >> auth
# 然后创建 secret
k -n teslamate create secret generic basic-auth --from-file=auth

创建 TLS Secret,这里使用已有的证书。

k  -n teslamate create secret tls example.com.tls --cert=./cert.pem --key=./cert.key

编写 Ingress 配置文件

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: teslamate-ingress
  namespace: teslamate
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
    nginx.ingress.kubernetes.io/proxy-body-size: 1000m
    # 最安全的是设置 IP 访问白名单
    # nginx.ingress.kubernetes.io/whitelist-source-range: 'x.x.x.x/x'
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - tesla.example.com
    secretName: example.com.tls
  rules:
  - host: tesla.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: teslamate
            port:
              number: 4000
      - path: /grafana # Grafana 的路由
        pathType: Prefix
        backend:
          service:
            name: grafana
            port:
              number: 3000

然后创建 Ingress

k -n teslamate apply -f ingress.yaml

获取 API Key

官方文档里提供了两种工具获取 API Key 的方式

这里以 Tesla Auth 为例,去 GitHub 仓库界面下载系统对应的安装包,解压后得到一个可执行文件。

cd Downloads/tesla_auth-aarch64-apple-darwin
chmod +x tesla_auth
sudo mv tesla_auth /usr/local/bin/

然后执行这个工具开始获取 API Key

Mac 第一次打开会提示不安全,需要去设置里手动允许执行。

tesla_auth

登录你的 Tesla 用户后就可以获取获取 Key,如果 UI 一直不刷新,可以看看终端的输出。

访问控制台

打开 https://tesla.example.com,输入设置的 Basic Auth 的用户名和密码即可打开控制台。

在这里输入刚刚获取的 Access Token 和 Refresh Token,等待片刻即可看到你的车辆。这里的地图使用的是 openstreetmap 的数据,国内可能访问不到,需要使用 Proxy 才能展示。

首次登录可能是英文,需要在右上角的设置里,设置一下语言为中文,同时如果是磷酸铁锂电池的车辆,也需要把电池类型的开关打开。

最后在最下方的 URLs 设置里,填入 Grafana 的控制台访问地址,否则首页不会展示控制台按钮。

https://tesla.example.com/grafana

然后就可以访问 Grafana 面板了,输入配置的用户名和密码登录即可。刚启动的项目没有太多数据,很多都会显示为 Null,等多运行几天后数据应该就会陆续产生了。

接入自定义面板

TeslaMate 有一个三方 Grafana 大屏的项目 Teslamate-CustomGrafanaDashboards,提供了另一种风格的图表,有需要也可以接入。

# 首先克隆项目
git clone https://github.com/jheredianet/Teslamate-CustomGrafanaDashboards.git
cd Teslamate-CustomGrafanaDashboards

创建一个 configmap,用来指定自定义面板的配置文件路径。

k -n teslamate create configmap customdashboards --from-file=customdashboards.yml

再创建一个 PVC,用来存放自定义面板的数据。这里为了方便放文件,使用的 SC 是 nfs-client,如果只有 longhorn 需要先创建再挂载并获取文件了。

# cusdashboard-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: teslamate-grafana-dashboard-pvc
  namespace: teslamate
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: nfs-client
  resources:
    requests:
      storage: 1Gi
# 创建 PVC
k apply -f cusdashboard-pvc.yaml
# 查看创建的 PV 的路径
k describe pv $(k get pv | grep grafana-dashboard | awk '{print $1}') | grep Path
# 进入储存路径,复制面板配置到储存里
cd /data/nfs/teslamate-teslamate-grafana-dashboard-pvc-pvc-xxxxx
cp -r ~/Teslamate-CustomGrafanaDashboards/dashboards .

然后修改 grafana.yaml,挂载配置和面板文件。

...
        volumeMounts:
        - name: teslamate-grafana-data
          mountPath: /var/lib/grafana
        - name: dashboard-config-volume
          mountPath: /etc/grafana/provisioning/dashboards/customdashboards.yml
          subPath: customdashboards.yml
        - name: dashboards-volume
          mountPath: /TeslamateCustomDashboards
      volumes:
      - name: teslamate-grafana-data
        persistentVolumeClaim:
          claimName: teslamate-grafana-data-pvc
      - name: dashboards-volume
        persistentVolumeClaim:
          claimName: teslamate-grafana-dashboard-pvc
      - name: dashboard-config-volume
        configMap:
          name: customdashboards

...

重新创建 Grafana,登录控制台就可以看到新添加的面板了。


且乐生前一杯酒