K8S 1.29部署Nexus:StatefulSet、存储与备份恢复实战
发布时间:2026/10/26 20:48:52 锦皓数字建站

先说个真实感受第一次把 Nexus 从一台物理机上的 tar 包迁到云原生 Kubernetes 集群里我心里是有点打鼓的。制品仓库这种东西平时安安静静没人夸一旦挂十分钟整条流水线就得停下来等几十个研发同时在群里问“仓库是不是又不行了”。所以“K8S 1.29 版本 部署 Nexus”这个标题看着只是换了个跑法实质是一次有状态服务的迁移工程牵扯到存储、权限、内存、暴露方式、备份恢复一整条链路。我前后在测试集群和一套小规模生产环境里各跑过一遍踩的坑不算少这里把完整思路和可抄的配置摊开讲。适合三类人看正在评估要不要把 Nexus 搬进 K8s 的运维刚接手制品仓库、需要快速搭一套可用的开发自测环境的同学以及已经把 Pod 跑起来了、但被 OOMKilled、探针失败、上传 413 折腾过的同行。1. 需求拆解Nexus 进 K8s到底在解决什么问题先把动机说清楚。制品仓库管理工具 Nexus 承担的是 Maven、npm、PyPI、Docker、Go module 这些依赖的统一收口它的核心特征是数据只增不减、单实例强一致、启动慢、磁盘 IO 密集。这四个特征决定了它在 Kubernetes 里的部署方式不能照抄无状态服务的模板否则后面全是麻烦。1.1 三类典型场景先判断你属于哪一类我在不同团队见过三种落地方式需求差别很大配置策略也完全不同。第一种是开发自测环境。团队只有十几个人制品量在几十 GB 级别要求是随时能起一套、用完就删。这种场景我会毫不犹豫用单副本 StatefulSet 加一个动态供给的 PVC资源给 2 核 4GB探针放宽甚至不做 Ingress直接kubectl port-forward用。别小看 port-forward临时排障比配域名快得多。第二种是团队级共享仓库。几十到上百人日增制品几个 GB需要 HTTPS 域名、需要接到 CI 里、需要账号体系和权限分组。这时候就得认真做 Ingress、资源限制、备份任务但副本数依然只能是 1。第三种是公司级唯一制品源。百万级组件、TB 级存储、有审计要求、有跨机房诉求。这种我反而不建议硬塞进普通 K8s 集群原因后面 1.2 会讲至少要独立的存储类和独立的节点池。判断清楚属于哪一类直接决定了你愿意为它花多少运维成本。我见过最浪费的做法是拿开发自测的配置去跑公司级仓库PVC 给了 50GB三个月后爆盘扩容量还得停机。1.2 有状态服务的部署形态为什么是 StatefulSet 而不是 Deployment这个问题我被问过很多次。Nexus 只有一个副本从功能上 Deployment 完全可以跑为什么还要用 StatefulSet答案在“身份稳定性”和“存储绑定关系”上。Deployment 的 Pod 名字是随机哈希重建之后变成另一个名字StatefulSet 的 Pod 名字是nexus-0序号固定配合volumeClaimTemplates声明的存储删除 Pod 再重建时会自动挂回同一个 PVC。这个行为在滚动更新、节点故障漂移的场景下非常关键你不会因为一次节点重启就丢挂载关系、重新走一遍初始化。更实际的一点是 StatefulSet 默认的更新策略是RollingUpdate且按序号倒序单副本时表现为“先删旧的再建新的”天然符合 Nexus 不能双写的要求。如果你非要用 Deployment务必把strategy.type改成Recreate否则默认的RollingUpdate会在升级时同时存在两个 Pod 抢同一个 PVC轻则文件锁冲突重则数据目录写坏。注意Nexus 的数据目录不支持多实例共享写。任何“多副本提升可用性”的想法都要放弃它的高可用只能靠“快速重建 冷备 外部存储可靠”来实现不靠副本数。另外一个容易被忽略的点是podManagementPolicy。单副本时这个参数意义不大但如果你为了滚动维护临时起多个 Pod 做迁移务必设成OrderedReady别让它们同时启动。1.3 存储选型PVC 加 CSI 是底线hostPath 只配做试验Kubernetes 1.29 的集群里云厂商的 in-tree 存储插件基本已经清理完毕StorageClass 背后都是 CSI 驱动。这意味着你声明一个 PVC实际落到哪块盘、性能如何完全取决于 StorageClass 的配置。我给 Nexus 选存储有三条硬标准。第一必须是块存储或高性能文件存储随机读写延迟要低因为 Nexus 的 blob store 是大量小文件加数据库随机写。第二必须支持在线扩容allowVolumeExpansion: true制品仓库的容量增长几乎不可能预估准确。第三回收策略设成RetainPV 被误删时数据还在能救回来。很多人图省事用 NFS。NFS 能跑但有两个坑一是fsGroup在 NFS 上经常不生效因为服务端不认客户端的 chown容器里 200 这个 UID 写不进去容器直接 CrashLoopBackOff二是小文件多的场景下元数据操作慢Nexus 启动时会扫一遍数据目录NFS 上启动时间能从两分钟变成十几分钟。真要用 NFS必须确保服务端导出的目录 UID/GID 就是 200或者用no_root_squash配合 initContainer 修权限。hostPath 我会明确说不推荐用于任何有数据价值的环境。节点一换数据就找不到了节点磁盘满了Pod 直接被驱逐。它只适合你在本地 kind 或 minikube 上验证镜像能不能起来。2. 部署前的准备1.29 集群、存储与镜像准备工作做得细不细直接决定后面是“一次跑通”还是“反复调参”。这一章我把几个必须在动手前确认的点列清楚尤其是 1.29 上和旧版本行为不一致的地方。2.1 Kubernetes 1.29 上几个容易忽略的差异1.29 这个版本本身没有翻天覆地的变化但有几个默认行为会咬人。第一个是 Pod Security Admission。它在 1.25 就 GA 了1.29 里如果你给命名空间打了pod-security.kubernetes.io/enforce: restricted标签那么任何以 root 运行的容器都会被拒绝。Nexus 官方镜像本身就是以 UID 200 运行的这没问题问题出在很多人习惯加的 initContainer 修权限容器那个容器要跑chown必须 root。在 restricted 命名空间里它会被直接拦下报错信息还不明显只说“violates pod security”。解决办法是把命名空间设成baseline或者干脆不打标签用默认的privileged。第二个是 CSI 迁移完成。如果你是从 1.20 时代的老集群升上来的有些旧的 StorageClass 参数可能已经失效比如某些云盘驱动的type字段取值变了。建议在目标集群上先跑一个最小的 PVC 加 Pod 挂载测试确认存储链路通再上 Nexus。第三个是 ServiceAccount 的 Token。从 1.24 起就不再自动为 SA 生成长期 Secret1.29 完全沿用这个行为。如果你打算让 CI 用 SA 的 Token 去访问 Nexus 的 REST API 做自动化别再去kubectl get secret找那个带-token-后缀的 Secret 了用kubectl create token sa-name --duration24h现签一个短期的安全性反而更好。另外提醒一句1.29 的kubectl客户端版本要和集群版本对齐在正负一个小版本内否则kubectl create token、kubectl debug这些子命令可能行为不一致。2.2 命名空间、StorageClass 与 PVC 的容量规划命名空间单独开一个不要和业务混在一起。原因很简单Nexus 的 PVC 是Retain策略命名空间删除时 PVC 不会跟着删容易留下一堆孤儿卷没人管。单独一个命名空间叫artifact-repo之类的后面清理的时候一目了然。容量规划我给一个粗糙但好用的公式PVC 容量 ≈ 日增制品体积 × 保留天数 × 1.3那个 1.3 是给 blob store 索引、数据库、临时上传文件留的余量。举个例子团队每天往仓库里推 8GB 的构件保留 90 天那么8 × 90 × 1.3 ≈ 936GB实际申请 1TB 比较稳。如果实在估不准就按“先给 200GB开自动扩容”起步同时配一条 PVC 使用率超过 75% 的监控告警。我见过太多人只配了 80% 的告警结果 Nexus 在 78% 的时候因为临时文件写不进去先挂了。StorageClass 的选择上我给一个参考配置apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nexus-ssd provisioner: 你的 CSI 驱动名 parameters: type: 高性能盘类型 reclaimPolicy: Retain allowVolumeExpansion: true volumeBindingMode: WaitForFirstConsumer mountOptions: - discardvolumeBindingMode: WaitForFirstConsumer很重要它让 PV 等到 Pod 调度到某个节点后再创建避免跨可用区挂不上。reclaimPolicy: Retain是保命的别用 Delete。2.3 镜像与 JVM 内存最容易翻车的地方镜像方面用官方发布的nexus3镜像标签一定要钉死到具体小版本比如3.68.1绝对不要用latest。原因很现实Nexus 的同一个小版本内也可能有数据目录格式微调用latest意味着某天 Pod 重启后自动拉了个新版本然后对着旧数据目录做迁移迁移失败就是起不来。我一般会把镜像先同步到内部镜像仓库然后 Pod 里引用内部地址这样即使外网拉不动也不影响重建。然后是内存这是最大的坑。这个镜像的默认 JVM 参数写在/opt/sonatype/nexus/bin/nexus.vmoptions里默认值大致是-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize2703m也就是说什么参数都不改的话JVM 堆要 2.7GB直接内存要 2.7GB加上 Metaspace 和线程栈容器实际需要 6GB 以上。如果你按“一个小服务给 2GB”的惯性思维去配limits.memory: 2Gi结果必然是容器被 OOMKilled而且日志里看不到任何 Java 异常只有Exit Code 137。正确做法是用INSTALL4J_ADD_VM_PARAMS环境变量显式覆盖参数追加在默认值之后同名参数以后出现的为准。一个 2GB 堆的配置长这样env: - name: INSTALL4J_ADD_VM_PARAMS value: - -Xms2g -Xmx2g -XX:MaxDirectMemorySize2g -XX:UseG1GC -Djava.util.prefs.userRoot/nexus-data/javaprefs -Duser.timezoneAsia/Shanghai-Djava.util.prefs.userRoot这一行是消除启动日志里那条Could not create the Java preferences directory警告的不影响功能但看着清爽。user.timezone影响的是服务端日志和定时任务的时间UI 上显示的时间由浏览器决定。这两个细节很多教程都不提。内存总量的估算公式我总结成这样容器 limit ≈ Xmx MaxDirectMemorySize 512Mi(Metaspace) 256Mi(线程栈/GC/其他)2GB 堆对应大约 5GB给limits.memory: 8Gi有比较充足的页缓存空间requests.memory: 4Gi让调度器知道这是个中等偏大的服务。CPU 方面requests: 1、limits: 4就行Nexus 大多数时间是空闲的但在做仓库重建索引reindex或者被 CI 并发拉取时会短时打满。注意requests和limits差距不要拉太大尤其是内存。K8s 的 QoS 分级里requests不等于limits的 Pod 属于 Burstable节点内存紧张时优先被驱逐。Nexus 被驱逐一次就要重启两三分钟不值得省那点资源。3. 分步实操从 PVC 到能登录的 Nexus前面是判断和规划这一章直接上配置。我把完整流程拆成五步每一步都能单独验证出了问题好定位。3.1 创建命名空间与存储声明先建命名空间然后建 PVC。这里我用独立 PVC 而不是volumeClaimTemplates理由是后面做快照备份、扩容、迁移时独立 PVC 操作起来更直观也不会因为删了 StatefulSet 而担心卷被一起回收。apiVersion: v1 kind: Namespace metadata: name: artifact-repo labels: app.kubernetes.io/part-of: artifact-repo --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nexus-data namespace: artifact-repo spec: accessModes: - ReadWriteOnce storageClassName: nexus-ssd resources: requests: storage: 200GiaccessModes用ReadWriteOnce就够了。如果你的 CSI 驱动支持ReadWriteOncePod1.29 里已经是 beta可以试试它能从调度层面保证只有一个 Pod 挂这块盘防止误操作起的第二个 Pod 抢写。不过要注意不是所有驱动都实现了这个模式用之前先确认否则 PVC 会一直 Pending。建完立刻验证kubectl -n artifact-repo get pvc nexus-data -w必须等到状态是Bound再往下走。如果一直是Pending用kubectl describe pvc看 Events八成是 StorageClass 名字写错、CSI 驱动没装、或者WaitForFirstConsumer模式下还没有 Pod 引用它。3.2 处理目录权限initContainer 与 fsGroup 的双保险Nexus 容器以 UID 200、GID 200 运行数据目录/nexus-data必须能被这个用户写入。块存储刚挂上来的时候根目录通常是 root:root 755写入会失败表现是容器启动到一半退出日志里一堆Permission denied。最省事的办法是用 Pod 级别的securityContext设fsGroupsecurityContext: fsGroup: 200 fsGroupChangePolicy: OnRootMismatchkubelet 会在挂载后把卷的组改成 200 并给组加读写权限。fsGroupChangePolicy: OnRootMismatch是优化项意思是只有根目录的组不对时才去递归改权限不设的话每次挂载都递归 chown数据目录大了会卡住几十秒甚至几分钟。但fsGroup对 NFS 类存储不一定生效。所以我会再加一个 initContainer 兜底initContainers: - name: fix-permissions image: busybox:1.36 command: - sh - -c - | chown -R 200:200 /nexus-data chmod 755 /nexus-data securityContext: runAsUser: 0 volumeMounts: - name: nexus-data mountPath: /nexus-data这个容器跑完就退出开销很小。但要注意如果命名空间启用了restricted级别的 Pod SecurityrunAsUser: 0会被拒绝这时候要么降级到baseline要么把fsGroup调到能用为止。注意chown -R在首次挂载空盘时是毫秒级但在几十万文件的老数据目录上可能要跑十几分钟这段时间 Pod 一直卡在Init状态别以为是卡死了。3.3 编写 StatefulSet容器、环境变量与端口这是核心部分。我把关键字段都标了注释方便你对着改。apiVersion: apps/v1 kind: StatefulSet metadata: name: nexus namespace: artifact-repo spec: serviceName: nexus-headless replicas: 1 selector: matchLabels: app: nexus template: metadata: labels: app: nexus spec: securityContext: fsGroup: 200 fsGroupChangePolicy: OnRootMismatch initContainers: - name: fix-permissions image: busybox:1.36 command: [sh, -c, chown -R 200:200 /nexus-data chmod 755 /nexus-data] securityContext: runAsUser: 0 volumeMounts: - name: nexus-data mountPath: /nexus-data containers: - name: nexus image: nexus3:3.68.1 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 8081 - name: docker containerPort: 8082 env: - name: INSTALL4J_ADD_VM_PARAMS value: - -Xms2g -Xmx2g -XX:MaxDirectMemorySize2g -XX:UseG1GC -Djava.util.prefs.userRoot/nexus-data/javaprefs -Duser.timezoneAsia/Shanghai resources: requests: cpu: 1 memory: 4Gi limits: cpu: 4 memory: 8Gi volumeMounts: - name: nexus-data mountPath: /nexus-data startupProbe: httpGet: path: /service/rest/v1/status port: http periodSeconds: 10 failureThreshold: 60 readinessProbe: httpGet: path: /service/rest/v1/status port: http periodSeconds: 15 timeoutSeconds: 5 failureThreshold: 3 livenessProbe: httpGet: path: /service/rest/v1/status port: http periodSeconds: 30 timeoutSeconds: 10 failureThreshold: 5 volumes: - name: nexus-data persistentVolumeClaim: claimName: nexus-data几个要点展开说一下。startupProbe的failureThreshold: 60乘以periodSeconds: 10等于给足 600 秒的启动窗口。空库启动一般 90 到 150 秒但如果是从旧仓库迁过来的大数据目录或者节点磁盘 IO 比较差五分钟以上很常见。没有startupProbe的话livenessProbe会在启动期间就开始计时连续失败直接重启形成“启动中被打断、再启动、再被打断”的死循环这是最经典的坑。serviceName: nexus-headless指向一个无头 Service用来给 StatefulSet 提供稳定的网络标识。虽然单副本不太用得上但加上它没坏处kubectl exec的时候也更好找。image写的是nexus3:3.68.1实际使用时前面要补上你内部镜像仓库的地址。别用latest前面说过了。3.4 Service 与 Ingress把 8081 暴露出去Service 分两个一个普通 ClusterIP 给 UI 和 HTTP 制品仓库用一个无头 Service 给 StatefulSet 用。如果要用 Docker 仓库还得额外暴露 8082。apiVersion: v1 kind: Service metadata: name: nexus-headless namespace: artifact-repo spec: clusterIP: None selector: app: nexus ports: - name: http port: 8081 targetPort: http --- apiVersion: v1 kind: Service metadata: name: nexus namespace: artifact-repo spec: type: ClusterIP selector: app: nexus ports: - name: http port: 8081 targetPort: http - name: docker port: 8082 targetPort: dockerIngress 用主流 Ingress Controller 的写法apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nexus namespace: artifact-repo annotations: nginx.ingress.kubernetes.io/proxy-body-size: 0 nginx.ingress.kubernetes.io/proxy-read-timeout: 600 nginx.ingress.kubernetes.io/proxy-send-timeout: 600 nginx.ingress.kubernetes.io/ssl-redirect: true spec: ingressClassName: nginx tls: - hosts: - nexus.example.internal secretName: nexus-tls rules: - host: nexus.example.internal http: paths: - path: / pathType: Prefix backend: service: name: nexus port: name: http三个注解缺一不可。proxy-body-size: 0关掉上传体积限制否则 CI 推一个 200MB 的 fat jar 就会收到 413报错信息还藏在响应体里前端只显示“上传失败”。proxy-read-timeout和proxy-send-timeout调到 600 秒是因为大制品上传加上服务端做 checksum 校验很容易超过默认的 60 秒。Docker 仓库的暴露要单独处理。Nexus 里创建的 Docker hosted 仓库会绑定一个 connector 端口比如 8082。你需要为它单独建一个 Ingresshost 用docker.nexus.example.internal后端指向 Service 的docker端口。这样docker push docker.nexus.example.internal/myapp:1.0就能走通。注意Docker 客户端默认要求 HTTPS如果走 HTTP 必须在每台机器的 daemon 配置里加insecure-registries这在多节点 CI 环境里维护成本很高。老老实实配 TLS 证书省事得多。3.5 首次登录与基础配置Pod 起来之后先拿初始密码kubectl -n artifact-repo exec nexus-0 -- cat /nexus-data/admin.password用admin加这个密码登录第一件事就是改密码然后去“设置 - 安全 - 匿名访问”里关掉匿名写权限。匿名读在很多团队里是开着的方便拉公共依赖但匿名写必须关否则任何人都能往你的 release 仓库推包。第二步是配 Blob Store。默认的defaultblob store 在/nexus-data/blobs/default小规模够用。如果要把不同类型仓库分盘管理可以新建 blob store但这会引入多目录备份的复杂度我一般建议 500GB 以下就一个。第三步是仓库规划。一个典型的组合是maven-central做代理maven-releases和maven-snapshots做本地再建一个maven-public做组把前三个聚合起来。这样开发只需要在settings.xml里配一个maven-public地址就够了。npm、PyPI 同理。第四步是把 CI 的凭据配好。别用 admin 账号给 CI 用建一个专用用户只授予对应仓库的nx-repository-view-*-*-read/add/edit权限。这个细节看着小但一旦 CI 的凭据泄露影响面差别是数量级的。4. 生产化打磨探针、资源、备份与升级跑起来只是开始这一章讲的都是“出过一次事之后才补上的配置”。4.1 探针设计startupProbe 是必需品不是可选项前面 StatefulSet 里已经写了三个探针这里说背后的逻辑。/service/rest/v1/status这个接口返回 200 代表服务已经完成初始化、可以接受请求。注意它和/service/rest/v1/status/check不一样后者会检查更多子系统的健康状态在仓库重建索引期间可能返回非 200用它做 liveness 会导致“索引重建时 Pod 被重启”的惨剧。所以 liveness 和 readiness 我都用/status只是参数不同。readiness 的periodSeconds: 15、failureThreshold: 3意味着服务不可用大约 45 秒后从 Endpoints 摘除。这个时间比 K8s 默认的 1 秒级探测温和得多避免 GC 停顿导致的误摘除。liveness 的failureThreshold: 5、periodSeconds: 30即连续 150 秒无响应才重启。Nexus 在做大数据量 reindex 时偶尔会有几十秒的响应延迟这个阈值能容忍。我实际遇到过一个问题节点负载很高时探针的timeoutSeconds设成默认的 1 秒httpGet经常超时导致 Pod 反复重启。把timeoutSeconds调到 5 到 10 秒之后就稳了。探针不是越灵敏越好对慢启动、有停顿的服务宁可钝一点。4.2 资源 requests/limits 与 JVM 的对应关系再强调一遍这一条值得单独拎出来说因为它是线上事故的高频原因。JVM 的堆大小是固定的我们显式设了-Xmx2g它不会因为容器 limit 调大就自动变大。相反如果 limit 比 JVM 需要的总内存小容器会被内核 OOM Killer 干掉Java 层完全感知不到日志断在最后一行正常输出。所以配资源的时候先定堆大小再算总内存最后设 limit。顺序不能反。给几个常见规模的参考值制品规模XmxMaxDirectMemoryrequests.memorylimits.memoryrequests.cpulimits.cpu10 万组件以内2g2g4Gi8Gi1410 万到 50 万3g3g6Gi12Gi2650 万以上4g4g8Gi16Gi28堆超过 4g 之后一定要确认-XX:UseG1GC生效默认的 Parallel GC 在大堆上停顿时间会很难看。可以在容器里用jcmd pid VM.flags确认。还有一个隐性消耗是 page cache。Nexus 读写 blob 文件时内核会用空闲内存做文件缓存这部分算在容器的memory.current里但不计入memory.working_set所以看监控时以working_set为准。如果你的监控面板显示内存常年 95%先别慌看看是不是 page cache。4.3 备份与恢复任务备份加冷备两条腿走路Nexus 的备份方案我建议两套并行各有用途。第一套是在 Nexus 里建一个“Create backup”类型的任务定期把 blob store 和数据库导出一份到一个独立的备份目录。任务建好之后可以在 REST API 里查到它的 task id然后用一个 CronJob 定时触发apiVersion: batch/v1 kind: CronJob metadata: name: nexus-backup namespace: artifact-repo spec: schedule: 0 2 * * * concurrencyPolicy: Forbid jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: trigger image: curlimages/curl:8.5.0 env: - name: NEXUS_USER valueFrom: secretKeyRef: name: nexus-backup-cred key: username - name: NEXUS_PASS valueFrom: secretKeyRef: name: nexus-backup-cred key: password - name: TASK_ID value: 你的备份任务ID command: - sh - -c - | curl -sS -f -u $NEXUS_USER:$NEXUS_PASS \ -X POST http://nexus:8081/service/rest/v1/tasks/$TASK_ID/run这个方案的好处是不停机坏处是备份文件还在同一个 PVC 上磁盘一坏就一起没了。所以必须再加一步把备份目录同步到对象存储。可以在同一个 CronJob 里加一个 sidecar 做上传或者在存储层做快照。第二套是冷备用于重大版本升级前。做法是kubectl scale statefulset nexus --replicas0等 Pod 完全退出然后对 PVC 打快照或者起一个临时 Pod 把/nexus-datarsync 到备份位置。冷备最可靠因为它保证文件系统状态一致但需要停机窗口。恢复的时候流程反过来新建一个 PVC把备份数据写进去改 StatefulSet 引用的 PVC 名字然后启动。注意恢复之后 NUID、blob store 路径都要能对上所以别在恢复过程中改目录结构。注意一个常见误区是只备份/nexus-data/blobs不备份/nexus-data/db。数据库里存的是组件元数据、仓库配置、用户权限少了它blob 文件就只是一堆没有索引的二进制恢复不起来。4.4 版本升级的步骤与回滚Nexus 的小版本升级一般可以直接换镜像标签大版本之间要看发布说明。有一件事必须提前知道较新的 3.x 版本把内部数据库引擎做过一次更换升级之后数据目录的格式就变了回退到旧版本是起不来的。所以升级前的那次冷备不是可选项是强制项。我的升级流程是这样第一步看目标版本的迁移说明确认能不能从当前版本一步升上去。跨得太多的话先在测试环境用备份数据走一遍记录耗时。第二步做冷备打 PVC 快照。第三步把 StatefulSet 的镜像标签改掉kubectl apply。StatefulSet 会用新镜像重建 Pod启动过程中会自动做数据迁移。第四步盯着日志看。迁移期间日志里会有明显的版本号和迁移进度输出这段时间不要碰 UI也不要去触发任何任务。第五步迁移完成后验证登录、拉一个依赖、推一个测试包、跑一次 Docker pull。三件事都通了才算升级成功。回滚方案只有一个停 Pod恢复快照改回旧镜像标签。不要指望“新版本起不来就自动降级”Nexus 没有这个机制。5. 常见问题与排查实录这一章是我踩过的坑和帮别人排查过的案例按症状归类方便你直接对号入座。5.1 问题速查表症状大概率原因排查动作解决Pod 反复OOMKilledExit Code 137容器内存 limit 小于 JVM 总需求kubectl describe pod看 Last State调大 limit 或调小-Xmx启动卡在 Init 阶段很久initContainer 在递归 chown 大目录kubectl logs -c fix-permissions给 PVC 加fsGroupChangePolicy: OnRootMismatch容器启动后立刻退出日志 Permission denied数据目录属主不是 200kubectl exec进去ls -ln /nexus-data加 initContainer 或fsGroup一直Pending不调度PVC 没 Bound或资源不足kubectl describe pod看 Events检查 StorageClass、节点资源上传大文件返回 413Ingress 默认 body 限制看 Ingress Controller 日志加proxy-body-size: 0上传到一半超时Ingress 读写超时太短看客户端报错时间点proxy-read-timeout调大docker push报 401Docker 仓库 connector 没配认证检查 Nexus 仓库配置开启 basic auth 或配置 token启动时反复被 liveness 重启没有 startupProbe 或阈值太小kubectl describe看 Restart Count 与间隔加 startupProbe放宽阈值UI 能开但 REST 接口 503服务还在初始化curl /service/rest/v1/status等启动完成检查 readiness日志时间对不上JVM 时区是 UTC看日志时间戳加-Duser.timezoneAsia/Shanghai5.2 几个值得单独说的坑第一个坑是“内存看起来够但就是被杀”。有一种情况是容器里同时跑了 JVM 和别的进程或者 JVM 启动了多个 GC 线程栈内存加起来超过预期。排查方法是进容器执行kubectl -n artifact-repo exec -it nexus-0 -- sh -c jcmd 1 VM.flags | tr \n | grep -E MaxHeapSize|MaxDirectMemory看看实际生效的堆大小是不是你设的值。如果还是 2703m说明INSTALL4J_ADD_VM_PARAMS没生效可能是变量名拼错或者被镜像里的启动脚本覆盖了。第二个坑是 NFS 上的fsGroup不生效。表现是 initContainer 跑完 chown 成功了主容器一启动还是 Permission denied。原因是 NFS 服务端没开no_root_squash客户端 root 过去被映射成nobodychown 实际上是失败的但没报错。解决办法要么改服务端配置要么在 NFS 导出目录上直接把 UID 设成 200。第三个坑是节点磁盘打满导致的连锁反应。Nexus 的 PVC 一般够大但容器的临时目录、日志目录可能写在节点盘上。如果日志级别调成 DEBUG 忘了改回来几天就能写满节点。我的做法是给容器加ephemeral-storage的限制resources: requests: ephemeral-storage: 2Gi limits: ephemeral-storage: 10Gi超了会被驱逐虽然粗暴但比拖垮整个节点好。第四个坑是“重建索引期间所有请求变慢”。这是正常现象Nexus 在做 reindex 时会大量读 blob 文件CPU 和 IO 都上去了。不要在这个时候去做 liveness 相关的调整也不要重启 Pod重启之后索引重建要从头再来恶性循环。正确做法是在低峰期手动触发并且提前在 Ingress 层把超时调大。第五个坑是 Docker 仓库的docker login成功但push失败。八成是仓库的部署策略Deployment policy设成了Read-only或者Allow redeploy没开导致同一个 tag 推第二次被拒。这个错误信息在客户端只显示denied: requested access to the resource is denied很有迷惑性得去 Nexus 的 UI 里看具体仓库配置。一个小技巧分享一下。做临时排障的时候与其反复改 Ingress 再等生效不如直接开一个 port-forwardkubectl -n artifact-repo port-forward svc/nexus 8081:8081然后本地浏览器访问http://localhost:8081。这个通道不经过任何代理层能快速区分“是 Nexus 本身的问题”还是“Ingress 转发的问题”。我在排查 413 和 502 的时候基本都先用这招定位层次。还有一点如果你的集群开启了 Pod 驱逐和节点自动缩容记得给 Nexus 所在的 Pod 加一条PodDisruptionBudgetapiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: nexus namespace: artifact-repo spec: maxUnavailable: 0 selector: matchLabels: app: nexusmaxUnavailable: 0表示自愿驱逐比如节点维护 drain时必须等 Pod 主动让路而单副本的 Nexus 永远不会主动让路所以 drain 会卡住。这是个双刃剑保证了 Nexus 不被误杀但也意味着节点维护必须人工介入先手动迁移再 drain。我的处理方式是保留这个 PDB但在运维手册里写清楚“维护含 Nexus 的节点前先手动 scale 到 0 并做完备份”。宁可麻烦不可丢数据。最后说一个心态上的经验。制品仓库这类服务在 K8s 里追求“高可用”的性价比很低因为它的瓶颈往往在存储和启动时间上不在调度层。真正值得投入的是靠谱的存储、定期的备份验证、清晰的升级流程、以及一份写明白“出事了先做什么”的应急清单。把这四件事做扎实单副本跑几年不出事是完全正常的。我个人在实际操作中的体会是部署本身半小时就能搞定真正花时间的是把探针阈值调到合适、把备份恢复演练一遍、把 CI 的凭据权限收窄。这三件事做完才算真正把这套 Nexus 交给了团队。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。