qbittorrentvpn.yaml 9.2 KB

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