feat: update JDownloader deployment for VLAN 50 migration with enhanced network configuration and egress guard
Some checks failed
validate / lint (push) Failing after 1s

This commit is contained in:
Nik Afiq 2026-08-24 22:45:08 +09:00
parent 2f7ec5f5b1
commit 756fa120b3

View File

@ -1,6 +1,25 @@
# Apply: kubectl apply -f manifests/media/jdownloader.yaml
# Delete: kubectl delete -f manifests/media/jdownloader.yaml
# Description: JDownloader deployment with Ingress at jdownloader.home.arpa.
#
# *** VLAN 50 MIGRATION — DO NOT PUSH TO main WITHOUT SEPARATE, EXPLICIT
# APPROVAL — AND ONLY AFTER qBittorrent (manifests/media/
# qbittorrent.yaml) HAS BEEN LIVE, VALIDATED, AND SOAKED. plan.md is
# explicit: migrate qBittorrent first, soak, then JDownloader,
# independently soaked — do not batch them. ***
# Same VLAN 50/Multus design as qbittorrent.yaml (see that file's header
# for the full precondition list — pia-gateway, vlan50-parent, and
# manifests/multus/ all applied and verified first). One difference:
# JDownloader has no reliably-persistent, file-editable interface-bind
# setting the way qBittorrent's qBittorrent.conf does (its own
# preferences aren't a simple INI this repo can safely patch), so this
# migration relies on the network-namespace egress guard alone for
# enforcement, exactly as plan.md anticipates for JDownloader
# specifically ("application-level interface binding may be weaker or
# unavailable... the namespace egress guard is mandatory").
# `media` has selfHeal+automated sync already configured — pushing this
# file deploys it immediately. Rollback copy, not synced by any
# Application: manifests/media/rollback/jdownloader-gluetun.yaml.
apiVersion: apps/v1
kind: Deployment
metadata:
@ -17,66 +36,65 @@ spec:
metadata:
labels:
app: jdownloader
annotations:
k8s.v1.cni.cncf.io/networks: |
[{"name": "vlan50", "namespace": "downloads", "interface": "net1", "ips": ["10.10.50.11/24"]}]
spec:
# Hard-pinned to nik-debian, not just node-role: storage — see
# qbittorrent.yaml for why.
nodeSelector:
node-role: storage
containers:
- name: gluetun
image: qmcgaw/gluetun:v3.41
kubernetes.io/hostname: nik-debian
# No cluster DNS/CoreDNS resolution needed or provided here — see
# qbittorrent.yaml for the full reasoning (identical here: this pod
# never looks up an in-cluster service by name).
dnsPolicy: None
dnsConfig:
nameservers:
- "10.10.40.53"
initContainers:
# NET_ADMIN lives here ONLY. No qBittorrent-style config-bind
# sibling init container — see this file's header for why
# JDownloader relies on the egress guard alone.
- name: vlan50-egress-guard
image: nicolaka/netshoot:v0.11
command: ["/bin/sh", "/scripts/guard.sh"]
env:
- name: VLAN50_GATEWAY
value: "10.10.50.1"
- name: TECHNITIUM_IP
value: "10.10.40.53"
- name: POD_CIDR
value: "10.42.0.0/16"
- name: SERVICE_CIDR
value: "10.43.0.0/16"
- name: NODE_IP
value: "10.10.40.20"
- name: EXPECTED_VLAN50_IP
value: "10.10.50.11"
securityContext:
capabilities:
add:
- NET_ADMIN
env:
- name: VPN_SERVICE_PROVIDER
value: private internet access
- name: VPN_TYPE
value: openvpn
- name: SERVER_REGIONS
value: JP Tokyo,Taiwan,Hong Kong
# value: US Seattle,US Oregon,US Silicon Valley
- name: OPENVPN_USER
valueFrom:
secretKeyRef:
name: pia-credentials
key: OPENVPN_USER
- name: OPENVPN_PASSWORD
valueFrom:
secretKeyRef:
name: pia-credentials
key: OPENVPN_PASSWORD
- name: FIREWALL_OUTBOUND_SUBNETS
value: "10.42.0.0/16,10.43.0.0/16,10.10.40.0/24"
- name: BLOCK_IPV6
value: "on"
startupProbe:
exec:
command:
- /gluetun-entrypoint
- healthcheck
periodSeconds: 10
failureThreshold: 60
livenessProbe:
exec:
command:
- /gluetun-entrypoint
- healthcheck
periodSeconds: 30
failureThreshold: 3
readinessProbe:
exec:
command:
- /gluetun-entrypoint
- healthcheck
periodSeconds: 10
failureThreshold: 3
drop: ["ALL"]
# NET_RAW alongside NET_ADMIN: the guard script's
# arping-based duplicate-address and gateway-reachability
# checks need it — see
# vlan50-egress-guard-script.yaml's header comment.
add: ["NET_ADMIN", "NET_RAW"]
volumeMounts:
- name: tun
mountPath: /dev/net/tun
- name: guard-script
mountPath: /scripts
containers:
- name: jdownloader
image: jlesage/jdownloader-2:latest
ports:
- containerPort: 5800
# No securityContext capability restriction here, deliberately
# — jlesage/jdownloader-2 uses the same docker-baseimage-gui
# PUID/GID pattern as linuxserver's images (USER_ID/GROUP_ID
# below): starts as root, its own init chowns /config to the
# requested uid/gid and drops privileges from there. See
# qbittorrent.yaml's app container for the fuller version of
# this reasoning — same conclusion, same kind of image.
env:
- name: USER_ID
value: "1000"
@ -112,6 +130,12 @@ spec:
image: python:3.12-alpine
ports:
- containerPort: 9667
# Unlike the jdownloader container above, this is a plain
# Python base image with no PUID/GID privilege-drop machinery
# to preserve — dropping all capabilities here is safe.
securityContext:
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 5m
@ -149,8 +173,10 @@ spec:
# .torrent files aren't handled by JDownloader's crawler (no BT
# plugin), so fetch the raw file ourselves instead of writing a
# crawljob. This container shares gluetun's netns, so the fetch
# is still VPN-routed same as JDownloader's own downloads.
# crawljob. This container shares the pod's network namespace
# (and its vlan50-egress-guard init container's routes/rules),
# so the fetch is still VPN-routed same as JDownloader's own
# downloads.
if basename.lower().endswith('.torrent'):
safe_name = basename.replace('/', '_').replace('\\', '_')
try:
@ -183,10 +209,6 @@ spec:
http.server.HTTPServer(('0.0.0.0', 9667), Handler).serve_forever()
volumes:
- name: tun
hostPath:
path: /dev/net/tun
type: CharDevice
- name: config
hostPath:
path: /data/jdownloader
@ -195,6 +217,10 @@ spec:
hostPath:
path: /mnt/storage/dl
type: Directory
- name: guard-script
configMap:
name: vlan50-egress-guard-script
defaultMode: 365 # octal 0555, r-xr-xr-x
---
apiVersion: v1
kind: Service