Kubernetes Helm Chart

I ended up getting this set up a couple weekends ago. How exactly is it that you upgrade? I’m backing my deployment via an NFS store. Is it as simple as just pulling the latest docker image? Also, this set up is pretty awesome! Thank you for doing this.

Hi @mlebrun home assistant uses a file-based sqllite3 database when the recorder is turned-on. Please keep this in-mind when using NFS to persist the home-assistant storage. There is a decent chance that at some point there will be a sqllite database corruption due to it being saved to NFS.

  • Using helm, it’s pretty easy to update by hand using the helm upgrade command and assigning the proper value to the home assistant image tag
  • Using a solution like keel you can automate the image upgrade
  • Using a gitops approach (like flux or others) you can also do something similar

Interesting, I didn’t realize that about sqlite and NFS. How are you persisting storage then? I think above it says you’re using rook/ceph? Wondering if there’s a simpler storage option as I don’t have much experience with either. Or maybe it’s a good excuse to learn!

Hm. Looks at the HA docs, apparently you can leverage the recorder component to write to a different DB: https://www.home-assistant.io/docs/backend/database/ Maybe I’ll give that a whirl.

@billimek do you have auth/login issues at all? HA can’t seem to keep me logged in. I simple refresh and I have to login and on top of that, half the time the init process after logging in hangs until I get a websocket timeout. Wasn’t sure if you experienced anything like that or not.

@mlebrun I’m not experiencing login issues especially - other than occasionally I need to force-refresh the page to get the UI to load. This may be after an upgrade though. FWIW, I also access Home Assistant via traefik so there is a layer in-between too.

When I’m at work and the corporate firewall blocks websocket connections, I can’t connect at all and must use my phone. So if you have something going on which is interfering with websocket connections, it could very well be related!

Hm, I’m not sure what the issue is but now I’ve at least got it to a more useable point. Sometimes I need to refresh/kill the browser tab a couple of times but at least I don’t need to keep entering my password/2FA. Thank you for the insight!

Hi @billimek - i realise the chat has moved on from when you posted this, but its moved on in a direction i dont really understand yet (just started fiddling with K8s at home this weekend!). My question - which seems to be incredibly relevant to what i quoted you on above, is how to make a /config directory mount possible within K8s? I had this working just fine with docker run with the -v switch:
-v /_LOCALDATA/HomeAssistantConfig:/config )
and then later with docker-compose
volumes:
- '/_LOCALDATA/HomeAssistantConfig:/config'
but what appeared simple at those layers, now seems quite complex in K8s. I started down the path of configmap:
kubectl create configmap k-home-assistant-config --from-file=/_LOCALDATA/HomeAssistantConfig/
but that just hangs.

would be greatful for a steer in the right direction.
End game is to follow your setup - have it all sourced from git, but i feel i need to walk before i can run.

im on a raspberry Pi BTW.
Thanks.
Keiran.

Just following on from the above, i managed to work out PV and PVC’s to the underlying node/host filesystem, so i can get at the /config mount (and yes, i know you guys are doing it slicker than that with git… ill get there) but now ive hit the next roadblock, which i think for me, may be a showstopper running HomeAssistant under kube.

As im running zWave, I need access to my Aeotech zWave USB stick (/dev/ttyXXXX) on the underlying node from kube.

In docker this was possible (docker run --device switch) but in kube, i believe this is trickier. I believe the framework might be ‘Device Plugins’ ( https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/ ). Wondering if anyone has gotten such a setup to work. Figured this, being a kube HA thread would be a great place to start! Otherwise it might spell the end of my kube adventures for HA and ill have to fall back to invoking fron docker.

Grateful for any advice.
Keiran.

Are any of you who are familiar with helm charts interested in becoming co-maintainers of the Home Assistant helm chart?

The idea is for multiple people knowledgeable about charts and Home Assistant will be able to review pull-requests. Mechanically this means adding a github username (and email) to the chart definition of the new maintainer(s).

Any interest?

2 Likes

You could add me… I added a lot of features lately.

Well, I’ve been using Home-Assistant via Docker (Compose) for years now. And just yesterday got CKA and CKAD certified.

I’m currently planning to port my whole infra to k8s, so just found your chart.

When I’m actually using it, I’ll let you know and I’d love to contribute!

Did you ever happen to work this out? I had updated my own home-assistant template to include passing through a device but i’m not keen on maintaining it – would much rather leverage the public one…

@billimek – thanks again for sharing all of your work. Next I may try and take some of the learnings and integrate into Azure Devops Build and Release pipelines.

My deployment.yaml which enables ZWave passthrough below below. It would also require a node selector configuration to make sure it got placed on the node that had the USB dongle.

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: {{ template "home-assistant.fullname" . }}
  labels:
    app: {{ template "home-assistant.name" . }}
    chart: {{ template "home-assistant.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ template "home-assistant.name" . }}
      release: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ template "home-assistant.name" . }}
        release: {{ .Release.Name }}
    spec:
      {{- with .Values.image.pullSecrets }}
      imagePullSecrets:
      {{- range . }}
        - name: {{ . }}
      {{- end }}
      {{- end }}
      {{- if .Values.hostNetwork }}
      hostNetwork: {{ .Values.hostNetwork }} 
      dnsPolicy: ClusterFirstWithHostNet
      {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          {{- if .Values.zwave.enabled }}
          securityContext:
            privileged: true
          {{- end }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: api
              containerPort: {{ .Values.service.port }}
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: api
            initialDelaySeconds: 60
            failureThreshold: 5
            timeoutSeconds: 5
          readinessProbe:
            httpGet:
              path: /
              port: api
            initialDelaySeconds: 30
            failureThreshold: 5
            timeoutSeconds: 5
          env:
            {{- range $key, $value := .Values.extraEnv }}
            - name: {{ $key }}
              value: {{ $value }}
            {{- end }}
            {{- range $name, $opts := .Values.extraEnvSecrets }}
            - name: {{ $name }}
              valueFrom:
                secretKeyRef:
                  name: {{ $opts.secret }}
                  key: {{ $opts.key }}
            {{- end }}
          volumeMounts:
          - mountPath: /config
            name: config
          {{- if .Values.zwave.enabled }}
          - mountPath: /dev/ttyACM0
            name: ttyacm
          {{- end }}
          resources:
{{ toYaml .Values.resources | indent 12 }}
        {{- if .Values.configurator.enabled }}
        - name: configurator
          image: "{{ .Values.configurator.image.repository }}:{{ .Values.configurator.image.tag }}"
          imagePullPolicy: {{ .Values.configurator.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.configurator.service.port }}
              protocol: TCP
          livenessProbe:
            tcpSocket:
              port: http
              initialDelaySeconds: 30
          readinessProbe:
            tcpSocket:
              port: http
              initialDelaySeconds: 15
          env:
            {{- if .Values.configurator.hassApiPassword }}
            - name: HC_HASS_API_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: {{ template "home-assistant.fullname" . }}-configurator
                  key: hass-api-password
            {{- end }}
            {{- if (.Values.configurator.username) and (.Values.configurator.password) }}
            - name: HC_USERNAME
              valueFrom:
                secretKeyRef:
                  name: {{ template "home-assistant.fullname" . }}-configurator
                  key: username
            - name: HC_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: {{ template "home-assistant.fullname" . }}-configurator
                  key: password
            {{- end }}
            {{- if .Values.configurator.hassApiUrl }}
            - name: HC_HASS_API
              value: "{{ .Values.configurator.hassApiUrl }}"
            {{- else }}
            - name: HC_HASS_API
              value: "http://{{ template "home-assistant.fullname" . }}:{{ .Values.service.port }}/api/"
            {{- end }}
            {{- if .Values.configurator.basepath }}
            - name: HC_BASEPATH
              value: "{{ .Values.configurator.basepath }}"
            {{- end }}
            {{- if .Values.configurator.enforceBasepath }}
            - name: HC_ENFORCE_BASEPATH
              value: "{{ .Values.configurator.enforceBasepath }}"
            {{- end }}
            {{- range $key, $value := .Values.configurator.extraEnv }}
            - name: {{ $key }}
              value: {{ $value }}
            {{- end }}
          volumeMounts:
          - mountPath: /config
            name: config
          resources:
{{ toYaml .Values.configurator.resources | indent 12 }}
        {{- end }}
      volumes:
      - name: config
      {{- if .Values.persistence.enabled }}
        persistentVolumeClaim:
          claimName: {{ if .Values.persistence.existingClaim }}{{ .Values.persistence.existingClaim }}{{- else }}{{ template "home-assistant.fullname" . }}{{- end }}
      {{- else }}
        emptyDir: {}
      {{ end }}
      {{- if .Values.zwave.enabled }}
      - name: ttyacm
        hostPath:
          path: /dev/ttyACM0
      {{- end }}
    {{- with .Values.nodeSelector }}
      nodeSelector:
{{ toYaml . | indent 8 }}
    {{- end }}
    {{- with .Values.affinity }}
      affinity:
{{ toYaml . | indent 8 }}
    {{- end }}
    {{- with .Values.tolerations }}
      tolerations:
{{ toYaml . | indent 8 }}
    {{- end }}

@carpenike would you consider submitting a pull request to the home assistant helm chart with the changes you came-up with for using the zwave tty device?

It may also be worth investigating a solution to share the zwave connection over the network instead of a direct-attached USB to the k8s node. This gives some hints using ser2net.

Yup let me re-work it with current master and test it out.

I had looked at the ser2net in the past. My only concern is that it wouldn’t re-connect if something happened with the remote device. Maybe we could build a healthprobe and recycle homeassistant if it comes back disconnected?

Also, long term fix for me will be zwave2mqtt along with zigbee2mqtt and put it on a pi.

UPDATE:

Resolved the issue. It appears that if the helm chart does not have the SecurityContext section down near the resources it doesn’t put it in the right space with the correct parameters.

In my case it moved it to the bottom but changed the userid to 0 and removed privileged: true. This resulted in the docker container not being deployed with the privileged flag.

PR submitted. Will leave the bottom below in case it’s helpful.


Having trouble with the passthrough as not all values are coming through. Anyone have any thoughts?

Helm Chart: https://github.com/carpenike/charts/tree/master/stable/home-assistant

Note that I also have a zigbee device attached to the same USB controller, so there’s two cdc_acm devices.

Passed through the USB PCI device from Proxmox to one of the k8s worker nodes:

Worker node sees the device:

ubuntu@k8s-1:~$ lsusb -t
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=uhci_hcd/2p, 12M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=ehci-pci/2p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/8p, 480M
        |__ Port 1: Dev 3, If 0, Class=Hub, Driver=hub/4p, 480M
            |__ Port 3: Dev 5, If 0, Class=Communications, Driver=cdc_acm, 12M
            |__ Port 3: Dev 5, If 1, Class=CDC Data, Driver=cdc_acm, 12M
        |__ Port 2: Dev 4, If 1, Class=CDC Data, Driver=cdc_acm, 12M
        |__ Port 2: Dev 4, If 0, Class=Communications, Driver=cdc_acm, 12M

Worker node also sees the pertinent info inside udevadm:

ubuntu@k8s-1:~$ udevadm info /dev/ttyACM0
P: /devices/pci0000:00/0000:00:10.0/usb1/1-1/1-1.2/1-1.2:1.0/tty/ttyACM0
N: ttyACM0
S: serial/by-id/usb-0658_0200-if00
S: serial/by-path/pci-0000:00:10.0-usb-0:1.2:1.0
E: DEVLINKS=/dev/serial/by-path/pci-0000:00:10.0-usb-0:1.2:1.0 /dev/serial/by-id/usb-0658_0200-if00
E: DEVNAME=/dev/ttyACM0
E: DEVPATH=/devices/pci0000:00/0000:00:10.0/usb1/1-1/1-1.2/1-1.2:1.0/tty/ttyACM0
E: ID_BUS=usb
E: ID_MM_CANDIDATE=1
E: ID_MODEL=0200
E: ID_MODEL_ENC=0200
E: ID_MODEL_FROM_DATABASE=Aeotec Z-Stick Gen5 (ZW090) - UZB
E: ID_MODEL_ID=0200
E: ID_PATH=pci-0000:00:10.0-usb-0:1.2:1.0
E: ID_PATH_TAG=pci-0000_00_10_0-usb-0_1_2_1_0
E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
E: ID_PCI_INTERFACE_FROM_DATABASE=EHCI
E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
E: ID_REVISION=0000
E: ID_SERIAL=0658_0200
E: ID_TYPE=generic
E: ID_USB_CLASS_FROM_DATABASE=Communications
E: ID_USB_DRIVER=cdc_acm
E: ID_USB_INTERFACES=:020201:0a0000:
E: ID_USB_INTERFACE_NUM=00
E: ID_VENDOR=0658
E: ID_VENDOR_ENC=0658
E: ID_VENDOR_FROM_DATABASE=Sigma Designs, Inc.
E: ID_VENDOR_ID=0658
E: MAJOR=166
E: MINOR=0
E: SUBSYSTEM=tty
E: TAGS=:systemd:
E: USEC_INITIALIZED=8463352

Worker node also properly maps the device to the cdc_acm driver:

ubuntu@k8s-1:~$ usb-devices

...

T:  Bus=01 Lev=03 Prnt=03 Port=02 Cnt=01 Dev#=  5 Spd=12  MxCh= 0
D:  Ver= 2.00 Cls=02(commc) Sub=00 Prot=00 MxPS=32 #Cfgs=  1
P:  Vendor=0451 ProdID=16a8 Rev=00.09
S:  Manufacturer=Texas Instruments
S:  Product=TI CC2531 USB CDC
S:  SerialNumber=__0X00124B0014D9E1BE
C:  #Ifs= 2 Cfg#= 1 Atr=80 MxPwr=50mA
I:  If#= 0 Alt= 0 #EPs= 1 Cls=02(commc) Sub=02 Prot=01 Driver=cdc_acm
I:  If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=cdc_acm

T:  Bus=01 Lev=02 Prnt=02 Port=01 Cnt=02 Dev#=  4 Spd=12  MxCh= 0
D:  Ver= 2.00 Cls=02(commc) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1
P:  Vendor=0658 ProdID=0200 Rev=00.00
C:  #Ifs= 2 Cfg#= 1 Atr=80 MxPwr=100mA
I:  If#= 0 Alt= 0 #EPs= 1 Cls=02(commc) Sub=02 Prot=01 Driver=cdc_acm
I:  If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=cdc_acm

...

Pod definition:

...
spec:
  containers:
  - image: homeassistant/home-assistant:0.94.3
    imagePullPolicy: IfNotPresent
    livenessProbe:
      failureThreshold: 5
      httpGet:
        path: /
        port: api
        scheme: HTTP
      initialDelaySeconds: 30
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 5
    name: home-assistant
    ports:
    - containerPort: 8123
      name: api
      protocol: TCP
    readinessProbe:
      failureThreshold: 5
      httpGet:
        path: /
        port: api
        scheme: HTTP
      initialDelaySeconds: 30
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 5
    resources: {}
    securityContext:
      procMount: Default
      runAsUser: 0
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /config
      name: config
    - mountPath: /dev/ttyACM0
      name: ttyacm
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-ghcc8
      readOnly: true
...
  volumes:
  - name: config
    persistentVolumeClaim:
      claimName: hass-home-assistant
  - hostPath:
      path: /dev/ttyACM0
      type: ""
    name: ttyacm
  - name: default-token-ghcc8
    secret:
      defaultMode: 420
      secretName: default-token-ghcc8
...

However, inside the pod it looks like a lot of the values have been stripped off the device:

ryan@RyMac:~$ kubectl exec -it hass-home-assistant-76c8bcd4c-tzmv4 -- sh
Defaulting container name to home-assistant.
Use 'kubectl describe pod/hass-home-assistant-76c8bcd4c-tzmv4 -n default' to see all of the containers in this pod.
# udevadm info /dev/ttyACM0
P: /devices/pci0000:00/0000:00:10.0/usb1/1-1/1-1.2/1-1.2:1.0/tty/ttyACM0
N: ttyACM0
E: DEVNAME=/dev/ttyACM0
E: DEVPATH=/devices/pci0000:00/0000:00:10.0/usb1/1-1/1-1.2/1-1.2:1.0/tty/ttyACM0
E: MAJOR=166
E: MINOR=0
E: SUBSYSTEM=tty

The container is able to see the cdc_acm driver though:

# lsmod |grep cdc_acm
cdc_acm                32768  0

usb-devices does look correct as well:

# usb-devices

...

T:  Bus=01 Lev=03 Prnt=03 Port=02 Cnt=01 Dev#=  5 Spd=12  MxCh= 0
D:  Ver= 2.00 Cls=02(commc) Sub=00 Prot=00 MxPS=32 #Cfgs=  1
P:  Vendor=0451 ProdID=16a8 Rev=00.09
S:  Manufacturer=Texas Instruments
S:  Product=TI CC2531 USB CDC
S:  SerialNumber=__0X00124B0014D9E1BE
C:  #Ifs= 2 Cfg#= 1 Atr=80 MxPwr=50mA
I:  If#= 0 Alt= 0 #EPs= 1 Cls=02(commc) Sub=02 Prot=01 Driver=cdc_acm
I:  If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=cdc_acm

T:  Bus=01 Lev=02 Prnt=02 Port=01 Cnt=02 Dev#=  4 Spd=12  MxCh= 0
D:  Ver= 2.00 Cls=02(commc) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1
P:  Vendor=0658 ProdID=0200 Rev=00.00
C:  #Ifs= 2 Cfg#= 1 Atr=80 MxPwr=100mA
I:  If#= 0 Alt= 0 #EPs= 1 Cls=02(commc) Sub=02 Prot=01 Driver=cdc_acm
I:  If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=cdc_acm

...

Here’s the error I get in Home Assistant startup logs:

2019-06-16 17:34:44 ERROR (MainThread) [homeassistant.config_entries] Error setting up entry Z-Wave (import from configuration.yaml) for zwave
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/openzwave/option.py", line 75, in __init__
    raise ZWaveException(u"Can't write to device %s : %s" % (device, traceback.format_exception(*sys.exc_info())))
openzwave.object.ZWaveException: "Zwave Generic Exception : Can't write to device /dev/ttyACM0 : ['NoneType: None\\n']"

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/src/app/homeassistant/config_entries.py", line 273, in async_setup
    hass, self)
  File "/usr/src/app/homeassistant/components/zwave/__init__.py", line 282, in async_setup_entry
    config_path=config.get(CONF_CONFIG_PATH))
  File "/usr/local/lib/python3.7/site-packages/openzwave/option.py", line 81, in __init__
    raise ZWaveException(u"Error when retrieving device %s : %s" % (device, traceback.format_exception(*sys.exc_info())))
openzwave.object.ZWaveException: 'Zwave Generic Exception : Error when retrieving device /dev/ttyACM0 : [\'Traceback (most recent call last):\\n\', \'  File "/usr/local/lib/python3.7/site-packages/openzwave/option.py", line 75, in __init__\\n    raise ZWaveException(u"Can\\\'t write to device %s : %s" % (device, traceback.format_exception(*sys.exc_info())))\\n\', \'openzwave.object.ZWaveException: "Zwave Generic Exception : Can\\\'t write to device /dev/ttyACM0 : [\\\'NoneType: None\\\\\\\\n\\\']"\\n\']'

UPDATE:

Also added /run/udev mount points to the container as well. That resolves the udevadm info error but it doesn’t fix the home assistant error:

root@hass-home-assistant-56b69bd8b4-rglg2:/config# udevadm info /dev/ttyACM0
P: /devices/pci0000:00/0000:00:10.0/usb1/1-1/1-1.2/1-1.2:1.0/tty/ttyACM0
N: ttyACM0
S: serial/by-id/usb-0658_0200-if00
S: serial/by-path/pci-0000:00:10.0-usb-0:1.2:1.0
E: DEVLINKS=/dev/serial/by-id/usb-0658_0200-if00 /dev/serial/by-path/pci-0000:00:10.0-usb-0:1.2:1.0
E: DEVNAME=/dev/ttyACM0
E: DEVPATH=/devices/pci0000:00/0000:00:10.0/usb1/1-1/1-1.2/1-1.2:1.0/tty/ttyACM0
E: ID_BUS=usb
E: ID_MM_CANDIDATE=1
E: ID_MODEL=0200
E: ID_MODEL_ENC=0200
E: ID_MODEL_FROM_DATABASE=Aeotec Z-Stick Gen5 (ZW090) - UZB
E: ID_MODEL_ID=0200
E: ID_PATH=pci-0000:00:10.0-usb-0:1.2:1.0
E: ID_PATH_TAG=pci-0000_00_10_0-usb-0_1_2_1_0
E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
E: ID_PCI_INTERFACE_FROM_DATABASE=EHCI
E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
E: ID_REVISION=0000
E: ID_SERIAL=0658_0200
E: ID_TYPE=generic
E: ID_USB_CLASS_FROM_DATABASE=Communications
E: ID_USB_DRIVER=cdc_acm
E: ID_USB_INTERFACES=:020201:0a0000:
E: ID_USB_INTERFACE_NUM=00
E: ID_VENDOR=0658
E: ID_VENDOR_ENC=0658
E: ID_VENDOR_FROM_DATABASE=Sigma Designs, Inc.
E: ID_VENDOR_ID=0658
E: MAJOR=166
E: MINOR=0
E: SUBSYSTEM=tty
E: TAGS=:systemd:
E: USEC_INITIALIZED=8463352

MinOZW on the worker node (VM) is able to connect to the device properly. Installed it in the container and it does not work. This seems to indicate a problem with the passthrough as the host operates fine.

Any thoughts? I did have this working before moving to Proxmox.

Great stuff, had a first successful attempt with a plain config.

Two questions on next steps:

  • ZWave; is it sufficient to specify zwave.enabled=true and zwave.device as it appears on the host ?
  • GIT: what is git.syncPath ? Is it /config or is it the path inside my git repo where the config is stored ?

Lars

Hey, great work. Would it be possible to make the exposed ports from the HA container in the deployment configurable? I need this to expose the port for the HomeKit integration.

Hi @mrkwtz, which ports in particular? I know that the home-assistant ‘default’ port of 3218 (where the API and GUI listen) is configurable already via this value.