I wanted to get OctoPrint running in my Kubernetes cluster to manage my 3D printer. Here is one way to accomplish that.
I am all in on Kubernetes, my entire network runs inside of it. I needed to upgrade my OctoPi installation and decided to install OctoPrint in my Kubernetes cluster instead. To do this, I took my old OctoPi Raspberry Pi, which was a Pi4 with 8 gigs of memory, and turned it into a Kubernetes worker node and slapped a taint and a label on it. I do this because I needed OctoPrint to run on the same Pi that the printer is connected to. There are 4 manifests, the deployment, the service, the ingress and the persistent volume claim. I will be covering those 4 manifests in this post.
Before we do anything with the manifests, we need to taint and label your worker node that your printer is connected to. We do this so we can make sure there are enough resources available to run OctoPrint (the taint) and that OctoPrint will always run on the correct worker node (the label). OctoPrint does not consume much memory at all so you may not necessarily need to taint your node. I'll leave that decision up to you.
Lets taint and label your node now. I am using
3dprinter as the taint and label, but you can do whatever you want, just modify the
deployment.yaml manifest accordingly.
kubectl taint nodes <nodename> dedicated=3dprinter:NoSchedule kubectl label nodes <nodename> dedicated=3dprinter
Now that your worker node has been isolated, we will set up the OctoPrint installation.
The persistent volume claim
First, we will cover the persistent volume claim. We need a persistent volume so that your plugins, recent print files, configuration, etc. gets saved across pod restarts. It is a simple PVC that will only request 10 gigs of space.
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: octoprint labels: app.kubernetes.io/name: octoprint app.kubernetes.io/instance: octoprint app.kubernetes.io/component: octoprint app.kubernetes.io/part-of: octoprint spec: accessModes: - ReadWriteMany volumeMode: Filesystem resources: requests: storage: 10Gi
Next, we will cover the deployment. This is where we create the pod, mount the volume and bring in the necessary devices like the serial port. If you need to bring in different devices take a look at how I did the
ttyACM0 and adjust accordingly.
apiVersion: apps/v1 kind: Deployment metadata: name: octoprint spec: selector: matchLabels: app.kubernetes.io/component: octoprint app.kubernetes.io/part-of: octoprint replicas: 1 revisionHistoryLimit: 1 template: metadata: labels: app.kubernetes.io/name: octoprint app.kubernetes.io/instance: octoprint app.kubernetes.io/component: octoprint app.kubernetes.io/part-of: octoprint spec: nodeSelector: dedicated: 3dprinter tolerations: - key: "dedicated" value: "3dprinter" effect: "NoSchedule" containers: - name: octoprint image: octoprint/octoprint:1.7.2 env: - name: ENABLE_MJPG_STREAMER value: 'false' securityContext: privileged: true resources: requests: memory: 2000Mi cpu: 1000m limits: memory: 3500Mi cpu: 2000m volumeMounts: - mountPath: /octoprint name: octoprint readOnly: false - mountPath: /dev/ttyACM0 name: ttyacm0 readOnly: false - mountPath: /dev/video0 name: video0 readOnly: false volumes: - name: octoprint persistentVolumeClaim: claimName: octoprint - name: ttyacm0 hostPath: path: /dev/ttyACM0 - name: video0 hostPath: path: /dev/video0
When I get my camera plugged in and going, I will change the
true. Since I do not have the camera mounted or plugged in, I have that off.
The service manifest is small and simple. It forwards port 80 on to the pod. You may notice the name is
octoprint-svc instead of just
octoprint. When it was
octoprint I was getting errors about invalid number format for the port. Turns out,
octoprint_port is the environment variable that tells OctoPrint what port to listen on. Kubernetes automatically brings in the services in the namespace as environment variables and sets them values pointing to the service, thus, breaking OctoPrint and causing a lot of head scratching.
apiVersion: v1 kind: Service metadata: # Can't use octoprint here because it sets OCTOPRINT_PORT to the ip/port of the service inside of the container # That environment variable is a config environment variable that tells octoprint what port to start on. name: octoprint-svc labels: app.kubernetes.io/name: octoprint app.kubernetes.io/instance: octoprint app.kubernetes.io/component: octoprint app.kubernetes.io/part-of: octoprint spec: selector: app.kubernetes.io/name: octoprint app.kubernetes.io/instance: octoprint app.kubernetes.io/component: octoprint app.kubernetes.io/part-of: octoprint ports: - protocol: TCP name: http port: 80 targetPort: 80 type: ClusterIP
The ingress manifest has a couple of unique properties. First thing to watch out for is if you use a custom SSL cert that is not publicly trusted, you will want to make sure that your OctoPrint ingress is not doing SSL redirection. I only tested with Prusa Slicer and it looked like it does some validation on the SSL certificate. The second thing to watch out for is the body size of the requests can be quite large when uploading print
gcode files. You will want to make sure that is not getting blocked either. I use NGINX for my ingress controller so my annotations are geared towards that. If you use something else you will need to figure those pieces out on your own.
Be sure to change the host name in the following manifest to whatever you want it to be.
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: octoprint-c-lan annotations: nginx.org/redirect-to-https: "False" ingress.kubernetes.io/ssl-redirect: "False" nginx.ingress.kubernetes.io/ssl-redirect: "False" nginx.ingress.kubernetes.io/proxy-body-size: 800m labels: app.kubernetes.io/name: octoprint app.kubernetes.io/instance: octoprint app.kubernetes.io/component: octoprint app.kubernetes.io/part-of: octoprint spec: ingressClassName: nginx tls: - hosts: - octoprint.example.com rules: - host: octoprint.example.com http: paths: - path: / pathType: ImplementationSpecific backend: service: name: octoprint-svc port: number: 80
This was a fun little project and completely overkill for what it is doing. But. I am a nerd, so, I did it.
Leave a comment if you have any comments or questions.