qbittorrentvpn.yaml 9.1 KB

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