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,登录控制台就可以看到新添加的面板了。
