| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- ---
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: qbittorrentvpn
- namespace: plex
- spec:
- strategy:
- type: Recreate
- selector:
- matchLabels:
- app: qbittorrentvpn
- replicas: 1
- template:
- metadata:
- labels:
- app: qbittorrentvpn
- annotations:
- backup.velero.io/backup-volumes-excludes: seedbox,media,media2,data-ec,scratch
- spec:
- affinity:
- nodeAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- nodeSelectorTerms:
- - matchExpressions:
- - key: seedbox
- operator: In
- values:
- - "true"
- tolerations:
- - key: seedbox
- operator: Equal
- value: "true"
- effect: NoSchedule
- containers:
- - name: qbittorrentvpn
- image: binhex/arch-qbittorrentvpn:5.1.0-1-01
- ports:
- - containerPort: 8080
- name: http-web-svc
- securityContext:
- privileged: true
- envFrom:
- - secretRef:
- name: qbittorrentvpn-secret
- livenessProbe:
- exec:
- command: ["curl", "--fail", "localhost:8080"]
- volumeMounts:
- - mountPath: "/data"
- name: seedbox
- - mountPath: "/media"
- name: media
- - mountPath: "/media2"
- name: media2
- - mountPath: "/dataec"
- name: data-ec
- - mountPath: "/config"
- name: config
- - mountPath: "/scratch"
- name: scratch
- volumes:
- - name: seedbox
- persistentVolumeClaim:
- claimName: seedbox-pvc
- - name: media
- persistentVolumeClaim:
- claimName: plex-pvc
- - name: media2
- persistentVolumeClaim:
- claimName: media2-pvc
- - name: data-ec
- persistentVolumeClaim:
- claimName: data-ec-pvc
- - name: config
- persistentVolumeClaim:
- claimName: qbittorrentvpn-pvc
- - name: scratch
- hostPath:
- path: /mnt/data/torrents
- type: Directory
- ---
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: qbittorrentvpn-exporter
- namespace: plex
- spec:
- strategy:
- type: Recreate
- selector:
- matchLabels:
- app: qbittorrentvpn-exporter
- replicas: 1
- template:
- metadata:
- labels:
- app: qbittorrentvpn-exporter
- spec:
- containers:
- - name: qbittorrentvpn-exporter
- image: ghcr.io/esanchezm/prometheus-qbittorrent-exporter:v1.6.0
- ports:
- - containerPort: 8000
- name: metrics
- envFrom:
- - secretRef:
- name: qbittorrentvpn-exporter-secret
- livenessProbe:
- exec:
- command:
- - "/bin/sh"
- - "-c"
- - 'wget -O - 0.0.0.0:8000 | grep -E "qbittorrent_up\{.* 1.0"'
- initialDelaySeconds: 3
- timeoutSeconds: 5
- periodSeconds: 3
- resources:
- requests:
- memory: "0"
- limits:
- memory: "256Mi"
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: qbittorrentvpn-service
- namespace: plex
- spec:
- selector:
- app: qbittorrentvpn
- type: ClusterIP
- ports:
- - name: qbittorrentvpn-web-port
- protocol: TCP
- port: 8080
- targetPort: http-web-svc
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: qbittorrentvpn-exporter-service
- namespace: plex
- labels:
- app: qbittorrentvpn-exporter
- spec:
- selector:
- app: qbittorrentvpn-exporter
- type: ClusterIP
- ports:
- - name: metrics
- protocol: TCP
- port: 8000
- targetPort: metrics
- ---
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: qbittorrentvpn
- namespace: plex
- annotations:
- traefik.ingress.kubernetes.io/router.entrypoints: websecure
- traefik.ingress.kubernetes.io/router.middlewares: kube-system-lanonly@kubernetescrd
- spec:
- rules:
- - host: qbittorrentvpn.lan.jibby.org
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- service:
- name: qbittorrentvpn-service
- port:
- number: 8080
- ---
- apiVersion: monitoring.coreos.com/v1
- kind: PrometheusRule
- metadata:
- labels:
- prometheus: qbittorrent
- role: alert-rules
- name: prometheus-qbittorrent-rules
- namespace: plex
- spec:
- groups:
- - name: ./qbittorrent.rules
- rules:
- - alert: QbittorrentErroredTorrents
- expr: sum(qbittorrent_torrents_count{status="error"}) > 0
- ---
- apiVersion: batch/v1
- kind: CronJob
- metadata:
- name: qbittorrentvpn-restart
- namespace: plex
- spec:
- schedule: "*/30 * * * *"
- successfulJobsHistoryLimit: 1
- failedJobsHistoryLimit: 1
- concurrencyPolicy: Forbid
- jobTemplate:
- spec:
- template:
- metadata:
- labels:
- app: qbittorrentvpn-restart
- spec:
- serviceAccountName: qbittorrentvpn-restart-serviceaccount
- securityContext:
- runAsUser: 1000
- runAsGroup: 1000
- restartPolicy: OnFailure
- containers:
- - name: qbittorrentvpn-restart
- image: python:3.14
- command:
- - python3
- - -c
- - |
- import subprocess
- import json
- import pprint
- import urllib.parse
- import sys
- import datetime
- # Vars to configure
- namespace = 'plex'
- qparams = {'labelSelector': 'app=qbittorrentvpn'}
- max_runtime = datetime.timedelta(days=1)
- # serviceaccount/k8s specific vars. Likely don't need to edit these.
- serviceaccount_dir = '/var/run/secrets/kubernetes.io/serviceaccount'
- apiserver = 'https://kubernetes.default.svc'
- token = open(f'{serviceaccount_dir}/token').read()
- result = subprocess.run([
- 'curl',
- '--cacert', f'{serviceaccount_dir}/ca.crt',
- '--header', f'Authorization: Bearer {token}',
- '-X', 'GET',
- f'{apiserver}/api/v1/namespaces/{namespace}/pods?{urllib.parse.urlencode(qparams)}'
- ],
- capture_output=True,
- check=True,
- )
- pod_list = json.loads(result.stdout)
- items = pod_list.get('items')
- if items is None or len(items) < 1:
- print(f'No pod found? Exiting. {pod_list=}')
- sys.exit(1)
- if len(items) > 1:
- print(f'>1 pod? Exiting. {items=}, {len(items)=}')
- sys.exit(1)
- pod = items[0]
- container_statuses = pod['status']['containerStatuses']
- if len(container_statuses) != 1:
- print(f'len(containerStatuses) != 1? Exiting. {container_statuses=}')
- sys.exit(1)
- running = container_statuses[0]['state'].get('running')
- if not running:
- print(f'Pod not running? Exiting. {container_statuses["state"]=}')
- started_at = datetime.datetime.fromisoformat(running["startedAt"])
- runtime = datetime.datetime.now(tz=datetime.UTC) - started_at
- print(f'{runtime=} > {max_runtime=} ? {runtime > max_runtime}')
- if runtime > max_runtime:
- pod_name = pod['metadata']['name']
- print(f'Deleting pod {pod_name}')
- result = subprocess.run([
- 'curl',
- '--cacert', f'{serviceaccount_dir}/ca.crt',
- '--header', f'Authorization: Bearer {token}',
- '-X', 'DELETE',
- f'{apiserver}/api/v1/namespaces/{namespace}/pods/{pod_name}'
- ],
- capture_output=True,
- check=True,
- )
- ---
- apiVersion: v1
- kind: ServiceAccount
- metadata:
- name: qbittorrentvpn-restart-serviceaccount
- namespace: plex
- ---
- apiVersion: rbac.authorization.k8s.io/v1
- kind: RoleBinding
- metadata:
- name: qbittorrentvpn-restart-serviceaccount-edit
- namespace: plex
- roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: edit
- subjects:
- - kind: ServiceAccount
- name: qbittorrentvpn-restart-serviceaccount
- namespace: plex
|