最近把一个媒体服务从单机 Docker Compose 迁到 K8s 测试环境。服务本身不复杂,但迁移过程中踩到几个很典型的问题:
- 镜像拉取慢。
- Pod 启动后媒体目录为空。
- PVC 挂载正常,但容器用户读不到文件。
- 页面能打开,但扫描不到媒体库。
这篇只记录排查过程。
目标结构
单机 Compose 里通常是:
volumes:
- /data/media/movies:/data/movies:ro
迁到 K8s 后,路径关系变成:
宿主机/存储系统 → PV → PVC → Pod volumeMount → 容器内路径
所以不能只问“目录在不在”,还要看 PVC 有没有正确挂进容器。
镜像预检
先不急着 apply Deployment,先确认节点能拉镜像:
kubectl get nodes
kubectl run image-check --image=<team-registry>/jellyfin:latest --restart=Never --command -- sleep 3600
kubectl describe pod image-check
kubectl delete pod image-check
如果这里已经 ImagePullBackOff,就先处理镜像仓库、DNS、代理或节点网络。
不要把镜像拉取失败误判成应用配置问题。
PVC 是否绑定
看 PVC:
kubectl get pvc
kubectl describe pvc media-pvc
状态必须是 Bound。
如果 PVC 没绑定,Pod 即使创建了,也不可能正常读取媒体目录。
Pod 里看挂载路径
Deployment 示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: jellyfin
spec:
replicas: 1
selector:
matchLabels:
app: jellyfin
template:
metadata:
labels:
app: jellyfin
spec:
containers:
- name: jellyfin
image: <team-registry>/jellyfin:latest
ports:
- containerPort: 8096
volumeMounts:
- name: media
mountPath: /data/movies
readOnly: true
- name: config
mountPath: /config
volumes:
- name: media
persistentVolumeClaim:
claimName: media-pvc
- name: config
persistentVolumeClaim:
claimName: jellyfin-config-pvc
进入 Pod:
kubectl exec -it deploy/jellyfin -- sh
ls -lah /data
ls -lah /data/movies
如果 /data/movies 为空,要回到 PV/PVC 和 StorageClass 排查。
应用后台填写容器内路径
K8s 里也一样。Jellyfin 后台应该填写:
/data/movies
而不是节点上的真实路径,也不是存储系统里的路径。
应用只认识容器内路径。
权限排查
如果容器内能看到目录,但应用读不到,继续看权限:
kubectl exec -it deploy/jellyfin -- id
kubectl exec -it deploy/jellyfin -- ls -lah /data/movies
必要时通过 securityContext 指定用户:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
不同存储插件对 fsGroup 的支持不完全一样,实际要结合 StorageClass 验证。
Service 和 Ingress 放到后面
如果媒体库为空,先别急着排 Ingress。
我的顺序是:
- 镜像能否拉取。
- Pod 是否 Running。
- PVC 是否 Bound。
- 容器内是否看到
/data/movies。 - 容器用户是否能读文件。
- Jellyfin 后台是否填写容器内路径。
- 最后再看 Service / Ingress。
Ingress 主要影响页面访问,不是媒体库扫描的第一嫌疑。
小结
Jellyfin 从 Docker Compose 迁到 K8s,最容易错的不是 YAML 语法,而是路径模型变了。
Compose 里是宿主机路径挂到容器路径。
K8s 里是 PV/PVC 再挂到容器路径。
应用最终看到的仍然只有容器内路径。
所以排查时别跳层:先镜像,再 PVC,再 volumeMount,再权限,最后才是应用和 Ingress。