qbittorrentvpn.yaml 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. ---
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. name: qbittorrentvpn
  6. namespace: plex
  7. spec:
  8. strategy:
  9. type: Recreate
  10. selector:
  11. matchLabels:
  12. app: qbittorrentvpn
  13. replicas: 1
  14. template:
  15. metadata:
  16. labels:
  17. app: qbittorrentvpn
  18. annotations:
  19. backup.velero.io/backup-volumes-excludes: seedbox,media,media2,data-ec,scratch
  20. spec:
  21. affinity:
  22. nodeAffinity:
  23. requiredDuringSchedulingIgnoredDuringExecution:
  24. nodeSelectorTerms:
  25. - matchExpressions:
  26. - key: seedbox
  27. operator: In
  28. values:
  29. - "true"
  30. tolerations:
  31. - key: seedbox
  32. operator: Equal
  33. value: "true"
  34. effect: NoSchedule
  35. containers:
  36. - name: qbittorrentvpn
  37. image: binhex/arch-qbittorrentvpn:5.1.0-1-01
  38. ports:
  39. - containerPort: 8080
  40. name: http-web-svc
  41. securityContext:
  42. privileged: true
  43. envFrom:
  44. - secretRef:
  45. name: qbittorrentvpn-secret
  46. livenessProbe:
  47. exec:
  48. command: ["curl", "--fail", "localhost:8080"]
  49. volumeMounts:
  50. - mountPath: "/data"
  51. name: seedbox
  52. - mountPath: "/media"
  53. name: media
  54. - mountPath: "/media2"
  55. name: media2
  56. - mountPath: "/dataec"
  57. name: data-ec
  58. - mountPath: "/config"
  59. name: config
  60. - mountPath: "/scratch"
  61. name: scratch
  62. volumes:
  63. - name: seedbox
  64. persistentVolumeClaim:
  65. claimName: seedbox-pvc
  66. - name: media
  67. persistentVolumeClaim:
  68. claimName: plex-pvc
  69. - name: media2
  70. persistentVolumeClaim:
  71. claimName: media2-pvc
  72. - name: data-ec
  73. persistentVolumeClaim:
  74. claimName: data-ec-pvc
  75. - name: config
  76. persistentVolumeClaim:
  77. claimName: qbittorrentvpn-pvc
  78. - name: scratch
  79. hostPath:
  80. path: /mnt/data/torrents
  81. type: Directory
  82. ---
  83. apiVersion: apps/v1
  84. kind: Deployment
  85. metadata:
  86. name: qbittorrentvpn-exporter
  87. namespace: plex
  88. spec:
  89. strategy:
  90. type: Recreate
  91. selector:
  92. matchLabels:
  93. app: qbittorrentvpn-exporter
  94. replicas: 1
  95. template:
  96. metadata:
  97. labels:
  98. app: qbittorrentvpn-exporter
  99. spec:
  100. containers:
  101. - name: qbittorrentvpn-exporter
  102. image: ghcr.io/esanchezm/prometheus-qbittorrent-exporter:v1.6.0
  103. ports:
  104. - containerPort: 8000
  105. name: metrics
  106. envFrom:
  107. - secretRef:
  108. name: qbittorrentvpn-exporter-secret
  109. livenessProbe:
  110. exec:
  111. command:
  112. - "/bin/sh"
  113. - "-c"
  114. - 'wget -O - 0.0.0.0:8000 | grep -E "qbittorrent_up\{.* 1.0"'
  115. initialDelaySeconds: 3
  116. timeoutSeconds: 5
  117. periodSeconds: 3
  118. resources:
  119. requests:
  120. memory: "0"
  121. limits:
  122. memory: "256Mi"
  123. ---
  124. apiVersion: v1
  125. kind: Service
  126. metadata:
  127. name: qbittorrentvpn-service
  128. namespace: plex
  129. spec:
  130. selector:
  131. app: qbittorrentvpn
  132. type: ClusterIP
  133. ports:
  134. - name: qbittorrentvpn-web-port
  135. protocol: TCP
  136. port: 8080
  137. targetPort: http-web-svc
  138. ---
  139. apiVersion: v1
  140. kind: Service
  141. metadata:
  142. name: qbittorrentvpn-exporter-service
  143. namespace: plex
  144. labels:
  145. app: qbittorrentvpn-exporter
  146. spec:
  147. selector:
  148. app: qbittorrentvpn-exporter
  149. type: ClusterIP
  150. ports:
  151. - name: metrics
  152. protocol: TCP
  153. port: 8000
  154. targetPort: metrics
  155. ---
  156. apiVersion: networking.k8s.io/v1
  157. kind: Ingress
  158. metadata:
  159. name: qbittorrentvpn
  160. namespace: plex
  161. annotations:
  162. traefik.ingress.kubernetes.io/router.entrypoints: websecure
  163. traefik.ingress.kubernetes.io/router.middlewares: kube-system-lanonly@kubernetescrd
  164. spec:
  165. rules:
  166. - host: qbittorrentvpn.lan.jibby.org
  167. http:
  168. paths:
  169. - path: /
  170. pathType: Prefix
  171. backend:
  172. service:
  173. name: qbittorrentvpn-service
  174. port:
  175. number: 8080
  176. ---
  177. apiVersion: monitoring.coreos.com/v1
  178. kind: PrometheusRule
  179. metadata:
  180. labels:
  181. prometheus: qbittorrent
  182. role: alert-rules
  183. name: prometheus-qbittorrent-rules
  184. namespace: plex
  185. spec:
  186. groups:
  187. - name: ./qbittorrent.rules
  188. rules:
  189. - alert: QbittorrentErroredTorrents
  190. expr: sum(qbittorrent_torrents_count{status="error"}) > 0
  191. ---
  192. apiVersion: batch/v1
  193. kind: CronJob
  194. metadata:
  195. name: qbittorrentvpn-restart
  196. namespace: plex
  197. spec:
  198. schedule: "*/30 * * * *"
  199. successfulJobsHistoryLimit: 1
  200. failedJobsHistoryLimit: 1
  201. concurrencyPolicy: Forbid
  202. jobTemplate:
  203. spec:
  204. template:
  205. metadata:
  206. labels:
  207. app: qbittorrentvpn-restart
  208. spec:
  209. serviceAccountName: qbittorrentvpn-restart-serviceaccount
  210. securityContext:
  211. runAsUser: 1000
  212. runAsGroup: 1000
  213. restartPolicy: OnFailure
  214. containers:
  215. - name: qbittorrentvpn-restart
  216. image: python:3.14
  217. command:
  218. - python3
  219. - -c
  220. - |
  221. import subprocess
  222. import json
  223. import pprint
  224. import urllib.parse
  225. import sys
  226. import datetime
  227. # Vars to configure
  228. namespace = 'plex'
  229. qparams = {'labelSelector': 'app=qbittorrentvpn'}
  230. max_runtime = datetime.timedelta(days=1)
  231. # serviceaccount/k8s specific vars. Likely don't need to edit these.
  232. serviceaccount_dir = '/var/run/secrets/kubernetes.io/serviceaccount'
  233. apiserver = 'https://kubernetes.default.svc'
  234. token = open(f'{serviceaccount_dir}/token').read()
  235. result = subprocess.run([
  236. 'curl',
  237. '--cacert', f'{serviceaccount_dir}/ca.crt',
  238. '--header', f'Authorization: Bearer {token}',
  239. '-X', 'GET',
  240. f'{apiserver}/api/v1/namespaces/{namespace}/pods?{urllib.parse.urlencode(qparams)}'
  241. ],
  242. capture_output=True,
  243. check=True,
  244. )
  245. pod_list = json.loads(result.stdout)
  246. items = pod_list.get('items')
  247. if items is None or len(items) < 1:
  248. print(f'No pod found? Exiting. {pod_list=}')
  249. sys.exit(1)
  250. if len(items) > 1:
  251. print(f'>1 pod? Exiting. {items=}, {len(items)=}')
  252. sys.exit(1)
  253. pod = items[0]
  254. container_statuses = pod['status']['containerStatuses']
  255. if len(container_statuses) != 1:
  256. print(f'len(containerStatuses) != 1? Exiting. {container_statuses=}')
  257. sys.exit(1)
  258. running = container_statuses[0]['state'].get('running')
  259. if not running:
  260. print(f'Pod not running? Exiting. {container_statuses["state"]=}')
  261. started_at = datetime.datetime.fromisoformat(running["startedAt"])
  262. runtime = datetime.datetime.now(tz=datetime.UTC) - started_at
  263. print(f'{runtime=} > {max_runtime=} ? {runtime > max_runtime}')
  264. if runtime > max_runtime:
  265. pod_name = pod['metadata']['name']
  266. print(f'Deleting pod {pod_name}')
  267. result = subprocess.run([
  268. 'curl',
  269. '--cacert', f'{serviceaccount_dir}/ca.crt',
  270. '--header', f'Authorization: Bearer {token}',
  271. '-X', 'DELETE',
  272. f'{apiserver}/api/v1/namespaces/{namespace}/pods/{pod_name}'
  273. ],
  274. capture_output=True,
  275. check=True,
  276. )
  277. ---
  278. apiVersion: v1
  279. kind: ServiceAccount
  280. metadata:
  281. name: qbittorrentvpn-restart-serviceaccount
  282. namespace: plex
  283. ---
  284. apiVersion: rbac.authorization.k8s.io/v1
  285. kind: RoleBinding
  286. metadata:
  287. name: qbittorrentvpn-restart-serviceaccount-edit
  288. namespace: plex
  289. roleRef:
  290. apiGroup: rbac.authorization.k8s.io
  291. kind: ClusterRole
  292. name: edit
  293. subjects:
  294. - kind: ServiceAccount
  295. name: qbittorrentvpn-restart-serviceaccount
  296. namespace: plex