news 2026/10/26 5:09:36

K8S 1.29 部署 Nexus 3:StatefulSet、存储与探针避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
K8S 1.29 部署 Nexus 3:StatefulSet、存储与探针避坑

Nexus 这套制品仓库,我前后在 K8s 上搭过好几回,每次都会在同样几个地方卡住:Pod 起来了但 Ready 一直是 0/1、日志里刷 Permission denied、上传一个几百兆的 Docker 镜像被网关 413 拦掉。这些问题单看都不复杂,可凑在一起就很容易让人怀疑是“云原生环境不兼容 Nexus”。实际情况恰恰相反——Nexus 在 Kubernetes 1.29 上跑得很稳,只是它是一套有状态、单写、重磁盘、慢启动的 Java 应用,用部署无状态服务的思路去套它,必然出问题。

这篇内容要解决的就是这件事:把 Nexus Repository 3 按正确姿势落到 K8S 1.29 集群上。适合两类人看——一类是刚开始接手内部制品仓库、想自己搭一套 Maven/npm/Docker 私服的开发者;另一类是已经在集群里跑着 Nexus、但总被 OOMKilled 或者探针误杀搞得不得安宁的运维同学。我会从“为什么这么选”讲到“YAML 每一行在干什么”,再到几个必踩坑的完整排查链路,尽量把话说到能直接抄作业的程度。

2. Nexus 在交付链路里的真实位置,决定了它不能当无状态服务部署

2.1 制品仓库卡住的到底是哪个环节

很多人对 Nexus 的印象还停留在“存 Maven jar 的地方”,实际上它现在承担的角色要重得多。一条典型的构建流水线里,它至少同时扮演三个身份:上游代理(把中央仓库、npm registry、Docker Hub 的产物缓存到内网,避免每次构建都出去拉)、内部发布点(团队自己的 SNAPSHOT、release 包、基础镜像版本都往这里推)、审计与合规的落点(哪个版本的构件被谁在什么时候下载过,出了供应链问题要能追溯)。

这三个身份带来的负载特征完全不同。代理身份是读多写少但突发极强,一次 CI 全量构建可能瞬间打进来几百个并发 GET;发布点是写少但对一致性极敏感,写坏了整个团队的构建都会挂;审计身份是长期留存,数据只增不减。把这三者压在一套 Nexus 上,意味着它的磁盘会持续膨胀、内存里要缓存大量元数据、并且任何一个写操作都不能丢。

这就解释了一个很常见的误判:有人看 Nexus 的 Pod “能跑起来”,就觉得它是个普通 Web 应用,随手用 Deployment + replicas: 2 部署。结果两个副本同时挂同一块盘,几天后数据库文件损坏,仓库直接打不开。Nexus 3 的元数据存储是嵌入式数据库,它没有为多实例并发写做任何设计,多副本共享存储等于自杀。

2.2 单写、重磁盘、慢启动:三个特征对应三套配置

我把 Nexus 的运行时特征拆成三条,每条都直接决定一个 K8s 配置项,这个映射关系理清了,后面的 YAML 就不用死记。

运行时特征具体表现对应的 K8s 配置
单写元数据库不允许并发写,多副本必损坏StatefulSet + replicas: 1,禁止 HPA
重磁盘blob store 随构件线性增长,IO 敏感PVC + 独立 StorageClass,容量预留 3 倍冗余
慢启动JVM 冷启动 + 元数据加载,首次可达 3~8 分钟startupProbe 而非 livenessProbe 兜底

“单写”这条最容易被违反,因为 K8s 的默认心智就是“副本越多越可靠”。但对 Nexus 来说,可靠性来自数据不损坏,不是来自副本数。真要高可用,正确做法是在前面挂只读镜像/缓存层,或者做仓库级的主备切换,而不是让两个 Pod 抢一块盘。

“慢启动”这条则是探针误杀的重灾区。Nexus 首次启动时 JVM 要加载大量类、初始化嵌入式数据库、扫描 blob store 目录建立索引,这个过程在数据量大的仓库上能拖到好几分钟。如果你只配了 livenessProbe,initialDelaySeconds 给个 60 秒,kubelet 会在第 60 秒开始探测,探不通就杀容器,容器重启后又要从头加载一遍——你会看到一个无限重启的循环,日志里却没有任何错误,因为每次都是被外部杀掉的。

2.3 K8S 1.29 这个版本号带来的实际差异

版本号不是随便写的,1.29 有几个变化会实打实影响部署。第一,flowcontrol.apiserver.k8s.io/v1beta2这个 API 在 1.29 中被移除,如果你打算用某个第三方 Helm chart 或者自研 Operator 来装 Nexus,而它依赖的 CRD 里还写着 v1beta2,kubectl apply会直接报 no matches for kind。上线前先kubectl api-resources | grep flowcontrol确认一下,比事后排查省事得多。

第二,LegacyServiceAccountTokenCleanUp在 1.29 默认开启,长时间未使用的 Secret 型 ServiceAccount token 会被自动清理。这条本身是好事,但如果你的 CI 流水线是靠在 Nexus 旁边挂一个长期 token 去做免密推送的,得确认那个 token 不是靠自动生成的 Secret 撑着的,否则某天早上构建突然 401。

第三,ReadWriteOncePod(RWOP)在 1.29 里已经是默认可用的 beta 能力。这个对我们特别有用——它能从准入层面保证一块 PVC 在同一时刻只被一个 Pod 挂载,等于给“禁止多副本”这条规则上了一把硬锁。下面讲到存储的时候我会展开。

需要说明的是,这些版本特性以官方 release notes 为准,我这里讲的是它对我们这套部署产生的具体影响,具体小版本可能还有微调,落地前扫一眼升级说明总没错。

3. 存储与状态的组合:StatefulSet、PVC 和那块绕不开的盘

3.1 为什么 Deployment + emptyDir 是错误答案

先说清楚错误答案长什么样,因为它是新手最自然的直觉。用 Deployment,数据目录挂 emptyDir,Pod 重建数据就没了——这个太明显,一般没人这么干。稍微“讲究”一点的版本是挂 hostPath 或者 NFS 的 subPath,看起来数据保住了,但埋了三个雷。

第一个雷是调度漂移。hostPath 绑定的是节点本地路径,Pod 被调度到另一台节点,路径就不存在了,或者更糟——存在但是空的,Nexus 会当成全新实例初始化,你的仓库“消失”了。要避免就得加 nodeSelector 钉死节点,那又牺牲了调度灵活性,节点故障时 Pod 起不来。

第二个雷是权限。Nexus 容器里以 UID 200 运行,hostPath 目录默认属主是 root,容器起来第一件事就是往/nexus-data写文件,直接 Permission denied。

第三个雷是扩容时的数据分裂。Deployment 滚动更新时新旧 Pod 可能短暂并存,两个进程同时操作同一个数据目录,这比多副本还危险。

StatefulSet 解决的正是“身份稳定”这件事:它给 Pod 一个稳定的序号(nexus-0)、稳定的网络标识、以及通过volumeClaimTemplates自动绑定到该序号的独立 PVC。即使 Pod 被删掉重建,它还是会拿回同一块盘,数据天然连续。对单实例应用来说,StatefulSet 带来的额外复杂度几乎为零,收益却是决定性的。

3.2 存储类怎么挑,容量怎么估

存储类的选择直接决定了后面权限坑的严重程度。我的经验是分三档来看:

存储类型适用场景注意点
块存储(云盘、Ceph RBD 等)生产首选IO 稳定,支持快照,扩容方便
NFS / 网络文件系统中小团队、测试环境fsGroup 行为不一致,需 initContainer 兜底
本地盘 / local-path单节点或边缘场景无副本,节点挂了数据就没了

块存储是生产环境的默认答案,原因不光是性能。它通常配套 VolumeSnapshot 能力,备份的时候一个 snapshot 就能拿到一致性数据,比你在容器里 tar 半天靠谱得多。NFS 的优势是多个 Pod 都能挂,但对 Nexus 这种单写应用,“能多挂”反而是风险。本地盘只建议在明确接受丢失风险的环境里用。

容量估算我给一个粗略公式,实测偏差不大:

  • Maven 私服:单构件平均 200KB~2MB,一个中型团队一年新增约 20~50GB(含 SNAPSHOT 历史版本)
  • npm 代理:单个包平均几 MB,热门包会被反复缓存,一年 10~30GB
  • Docker 镜像仓库:这是大头,单个镜像层几十到几百 MB,一个 50 人的团队一年轻松吃掉 200GB~1TB
  • 元数据与索引:按数据量的 5%~10% 预留

加起来之后,至少再乘 1.5 到 2 的系数。原因很实际:Nexus 的 blob store 在删除构件后不会立刻回收空间,需要手动跑 compact 任务;升级前你要留出快照空间;万一哪次大批量导入把盘打满,PVC 在线扩容虽然能做,但很多 CSI 扩容后有文件系统层面的等待期,业务会中断。宁可一开始申请大一点,云盘扩容容易,缩容几乎不可能。

3.3 用 RWOP 给“禁止多副本”上一把锁

前面说了 Nexus 不能多副本,但“知道不能”和“系统保证不能”是两回事。团队里总有新同学看到replicas: 1想改成 3 试试,或者写个 HPA 想让它弹性伸缩。这时候如果只是靠文档约束,早晚出事。

1.29 里的ReadWriteOncePod访问模式就是干这个的。把 PVC 的accessModes从ReadWriteOnce改成ReadWriteOncePod,语义立刻收紧:这块卷在同一时刻只能被单个 Pod 挂载,第二个 Pod 会因为卷已被占用而卡在 ContainerCreating。真要有人手贱改副本数,表现是第二个 Pod 起不来,而不是两个 Pod 一起把数据库写坏——故障从“数据损坏”降级成了“Pod Pending”,这个降级非常值。

有一点要注意:RWOP 需要 CSI 驱动支持。主流的块存储 CSI 基本都支持了,但一些老的 NFS provisioner 可能不支持,创建 PVC 时会直接报错。落地前先用一个测试 PVC 验证一下,别等到生产环境才发现。

配合 StatefulSet 的podManagementPolicy: OrderedReady(默认值)和updateStrategy: RollingUpdate,单副本场景下更新就是“先删旧的、再建新的”,中间有短暂中断,但数据绝对安全。对内部制品仓库来说,几十秒的不可用完全可以接受。

volumeClaimTemplates: - metadata: name: nexus-data spec: accessModes: ["ReadWriteOncePod"] storageClassName: csi-block-ssd resources: requests: storage: 500Gi

4. 把清单写对:从命名空间到 Service 的逐字段拆解

4.1 命名空间、镜像与那层容易漏掉的权限

先把外围准备好。命名空间本身没什么好说的,但有两个细节值得单独提。

镜像标签绝对不要用 latest。Nexus 的升级是有明确路径的,跨版本升级需要看对应的 upgrade notes,有些版本对存储引擎有迁移要求。用 latest 的后果是某天 Pod 重启,顺手拉了个新版本,然后仓库起不来了。写成具体的稳定 tag,比如sonatype/nexus3:3.65.0,你落地时换成当时的稳定版即可。

镜像拉取凭证这块,如果镜像放在私有 registry,记得创建imagePullSecrets。这一步本身简单,坑在于凭证过期后 Pod 重启才报错,而平时没人会去验证。建议在 CI 里加一个定时任务,每周拉一次镜像做连通性检查。

然后是安全上下文,这是第一个真正的坑点:

securityContext: runAsUser: 200 runAsGroup: 200 fsGroup: 200 fsGroupChangePolicy: OnRootMismatch

为什么是 200?因为 Nexus 官方镜像里的nexus用户就是 UID 200。你在容器里ls -ln /nexus-data能看到属主是200 200。挂载 PVC 之后,如果卷的根目录属主是 root(大部分 provisioner 的默认行为),Nexus 进程就没法在里面建目录、写数据库文件,启动会以java.io.IOException: Permission denied或Failed to create directory收场。

fsGroup: 200是让 kubelet 在挂载前把卷的组改成 200 并赋予组读写权限。这个机制对大多数 CSI 卷有效,但对 NFS 和一些第三方 provisioner 不一定生效。fsGroupChangePolicy: OnRootMismatch的意思是“只在根目录属主不对时才递归改”,这个优化很重要——数据目录里有几十万个文件的时候,每次启动都递归 chown 会让 Pod 卡在 ContainerCreating 好几分钟,甚至超时。

如果fsGroup不生效(判断方法是看 Pod 事件里有没有failed to set fsgroup,或者直接进容器看目录属主),就得用 initContainer 兜底:

initContainers: - name: fix-permissions image: busybox:1.36 command: ["sh", "-c", "chown -R 200:200 /nexus-data && chmod 750 /nexus-data"] securityContext: runAsUser: 0 volumeMounts: - name: nexus-data mountPath: /nexus-data

注意这个 initContainer 必须runAsUser: 0才能 chown,而主容器仍然是 200,两者不冲突。chown -R在大目录上很慢,所以更好的写法是只 chown 顶层目录,或者在首次部署时手动跑一次、之后注释掉。我曾经在一个 300GB 的仓库上让这个 initContainer 跑了四十分钟,Pod 一直 PreInit,看着像卡死,实际在老老实实改属主。

4.2 StatefulSet 里每个字段都在解决一个具体问题

完整的主容器定义大概是这样:

containers: - name: nexus image: sonatype/nexus3:3.65.0 ports: - name: http containerPort: 8081 - name: docker containerPort: 8082 env: - name: INSTALL4J_ADD_VM_PARAMS value: "-Xms4g -Xmx4g -XX:MaxDirectMemorySize=2g -Djava.util.prefs.userRoot=/nexus-data/javaprefs" - name: TZ value: "Asia/Shanghai" resources: requests: cpu: "1" memory: 6Gi limits: cpu: "4" memory: 10Gi startupProbe: httpGet: path: /service/rest/v1/status port: 8081 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 60 readinessProbe: httpGet: path: /service/rest/v1/status port: 8081 periodSeconds: 15 timeoutSeconds: 5 livenessProbe: httpGet: path: /service/rest/v1/status port: 8081 periodSeconds: 30 timeoutSeconds: 10 failureThreshold: 5

INSTALL4J_ADD_VM_PARAMS这个环境变量是关键。Nexus 打包用的是 install4j 启动器,它在启动时会读取nexus.vmoptions里的 JVM 参数,而这个环境变量的内容会被追加进去、并且优先级更高。为什么非要用它而不是改配置文件?因为配置文件在镜像里,改了不持久;挂 ConfigMap 覆盖又容易和版本升级时的默认值打架。用环境变量是最干净的覆盖方式。

-Djava.util.prefs.userRoot=/nexus-data/javaprefs这一项容易被漏掉。不加的话 Java 会往用户 home 目录写 preferences,而容器里 home 目录可能是只读的或者不可写的,日志里会出现一堆Couldn't flush user prefs的警告。不致命,但会污染日志,排查问题时很碍眼。

TZ环境变量是给操作审计用的。任务执行时间、日志时间戳都依赖它,默认 UTC 会让你在对照业务时间线时反复做心算。

端口这里要开两个。8081 是主 HTTP 端口,UI、REST API、Maven/npm 仓库都走它;8082 是给 Docker 仓库用的独立 connector 端口。很多人第一次配 Docker 私服时卡在这里——在 Nexus 里建了 docker hosted 仓库,connector 填了 8082,然后发现外部完全连不上,因为 Service 里根本没暴露这个端口。

4.3 Service 的两个端口与一个容易被忽略的细节

Service 定义很直白:

apiVersion: v1 kind: Service metadata: name: nexus spec: type: ClusterIP selector: app: nexus ports: - name: http port: 8081 targetPort: http - name: docker port: 8082 targetPort: docker

用 ClusterIP 而不是 NodePort,是因为对外暴露交给 Ingress 做,这样 TLS、域名、访问控制都统一在一层管理。只有当 Docker 客户端必须直连端口、而又不想为它单独配域名时,才考虑 LoadBalancer。

有一个细节值得单独说:Service 的 selector 必须和 StatefulSet 的 Pod 标签严格对上。StatefulSet 的spec.selector.matchLabels是不可变的,创建后改不了;如果你先建了 Service 再改标签,会出现 Service 有 Endpoints 但连不通、或者 Endpoints 为空的情况。排查命令是kubectl get endpoints nexus -n <ns>,看到<none>就说明标签对不上,别去怀疑网络插件。

另外,Nexus 的nexus.properties里有个上下文路径配置,默认是/。如果你打算让 Nexus 跑在子路径下(比如/nexus/),要在环境变量里设NEXUS_CONTEXT。这个改动会连带影响 REST API 的健康检查路径,探针里的/service/rest/v1/status要跟着改成/nexus/service/rest/v1/status,改漏了就是无休止的重启。我的建议是能不用子路径就不用,省掉这一整串连带修改。

5. 对外提供服务这一层:Ingress、TLS 与大文件上传的三个坎

5.1 Ingress 注解的最小可用集合

Nexus 挂在 Ingress 后面,最常见的三个故障是:上传大构件 413、长时间操作 504、下载断流。这三个都能用注解解决,但注解的取值需要理解原理。

annotations: nginx.ingress.kubernetes.io/proxy-body-size: "0" nginx.ingress.kubernetes.io/proxy-read-timeout: "1800" nginx.ingress.kubernetes.io/proxy-send-timeout: "1800" nginx.ingress.kubernetes.io/proxy-buffering: "off" nginx.ingress.kubernetes.io/proxy-request-buffering: "off"

proxy-body-size: "0"表示不限请求体大小。默认值通常是 1m,意味着超过 1MB 的构件上传直接被网关以 413 拒掉,而客户端的报错信息往往是含糊的 “unexpected EOF” 或者 “connection reset”,完全不提 413。这个坑我踩过不止一次,每次都要翻网关日志才能定位。

proxy-read-timeout和proxy-send-timeout拉到 1800 秒,是给大镜像推送留余量。一个几 GB 的镜像层在带宽一般的链路上传十几分钟很正常,默认 60 秒超时会在传到一半时断开,客户端报 “blob upload unknown”。

proxy-buffering: off和proxy-request-buffering: off这两项是性能优化。Nexus 传输大文件时如果用网关做缓冲,数据要先完整落到网关的临时目录再转发,等于把一份流量写了两遍盘,既慢又占空间。关掉之后是流式透传,内存占用也更可控。

还有一点:如果前面还有一层外部负载均衡或者 CDN,光改 Ingress 注解不够,那一层也有自己的 body size 和超时限制,得一起改。这种多层网关的配置不一致,是最难排查的一类问题,因为你在集群内怎么测都正常。

5.2 反代带来的 Host 头与协议头问题

Nexus 生成下载链接、重定向、以及某些 API 的返回体里,会带上它认为自己所在的外部地址。如果它以为自己是http://nexus.default.svc:8081,那返回给浏览器的链接就是内网地址,用户点开就 404 或者打不开。

解决办法有两个层面。Ingress 侧要正确传递头部:

nginx.ingress.kubernetes.io/use-forwarded-headers: "true"

Nexus 侧则要在仓库配置里指定 “Force base URL”。具体位置在 Repository 的 HTTP connector 配置里,填上对外的完整地址(比如https://nexus.example.internal)。这个配置是仓库级的,Docker 仓库和 Maven 仓库要分别设,漏一个就会出现“Maven 能拉、Docker 拉不了”的诡异现象。

另外,如果 Ingress 做了 TLS 终止(绝大多数情况都是),要确保传给后端的X-Forwarded-Proto是https。Nexus 在生成绝对 URL 时会用这个头判断协议,头不对就会生成 http 链接,浏览器在 https 页面里加载 http 资源会被拦,表现是页面能打开但样式和脚本全丢。

5.3 Docker 仓库接入的正确姿势

Docker 仓库这块值得单独展开,因为它是新手最容易卡住的地方。

第一个问题是端口独立性。Nexus 里创建 Docker 仓库时,connector 的 HTTP 端口必须和主端口 8081 不同,通常是 8082。这意味着你的 Service 必须暴露 8082,前面已经说过。但更深一层的问题是:Docker 客户端访问的是/v2/开头的 API 路径,它需要能直接路由到这个 connector 端口。

有两种接法。接法一是用 Ingress 按域名分流,把registry.example.internal的所有请求转发到 Service 的 8082 端口。这种方式走标准的 443 + TLS,客户端不需要任何额外配置,是最干净的。

接法二是直接用 NodePort 或 LoadBalancer 暴露 8082。这时候客户端连的是IP:8082,走的是明文 HTTP,需要在 Docker 的daemon.json里把这个地址加进insecure-registries。这种方式配置简单,但明文传输在生产环境里不合适,只建议在测试环境用。

第二个问题是路径前缀。Docker 仓库如果你在 Nexus 里给它配了docker-hosted这样的名字,客户端推送时的地址是registry.example.internal/docker-hosted/镜像名:tag,那个仓库名是路径的一部分。很多人以为只是给仓库起个显示名,结果推的时候不带这个前缀,报 “repository not found”。

第三个问题是认证。Docker 客户端和 Nexus 的认证交互走的是 token 流程,如果 Ingress 层开了某些缓存或者对Authorization头做了处理,会出现登录成功但推送 401 的情况。排查方法是直接在集群内用curl -v打一遍/v2/接口,看返回的WWW-Authenticate头是否正常透传。

6. 三个必踩坑的完整排查链路

6.1 权限:fsGroup 生效与否的判定方法

Pod 起不来的时候,第一件事是看日志:

kubectl logs nexus-0 -n nexus --previous

如果看到java.io.IOException: Permission denied或者Failed to create directory: /nexus-data/...,基本可以锁定是权限问题。但别急着加 initContainer,先确认 fsGroup 到底生效了没有:

kubectl exec -it nexus-0 -n nexus -- ls -ln /nexus-data

如果输出里属主是0 0(root),说明 fsGroup 没生效;如果是200 200但还是写不进去,那问题在别处——可能是卷被挂成了只读,或者 SELinux 标签不对。

还有一种情况是卷的根目录权限对了,但子目录不对。这通常发生在你之前用 root 跑过容器、或者在别的环境里往这块盘写过数据。这种情况下fsGroup只在挂载时改根目录,不会递归处理里面已有的内容,必须用 initContainer 递归 chown 一次。

排查顺序我习惯这样走:先看 Pod 事件(kubectl describe pod的 Events 段),有没有failed to set fsgroup或者mount failed;再看容器日志确认是不是到了写文件阶段才失败;最后 exec 进去看实际属主。三步做完基本就能定位,不用瞎猜。

6.2 内存:JVM 参数和容器 limit 的错配

Nexus 被 OOMKilled 的场景,十有八九是 JVM 堆参数和容器 memory limit 没对齐。

具体机制是这样的:Nexus 镜像里nexus.vmoptions默认写的是-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m。2703 这个数字看着像随手填的,但它是固定值,不会跟着容器 limit 自动调整(老版本镜像里有-XX:+UseCGroupMemoryLimitForHeap会读 cgroup,但新版 JVM 的行为变了,依赖它很不可靠)。

也就是说,如果你给容器 limit 设了 4Gi,JVM 认为堆能用 2.7G、直接内存还能再要 2.7G,加上元空间、线程栈、GC 自身的开销,很容易突破 4Gi。容器一超限,内核直接 kill,kubectl describe pod里显示Reason: OOMKilled,日志可能干净得什么都没有,因为进程是被 SIGKILL 干掉的,来不及写任何东西。

正确的配比是让 JVM 的堆 + 直接内存大约占 limit 的 60%~70%:

使用场景容器 limit-XmxMaxDirectMemory备注
个人/小团队试用4Gi2g1g仓库数量控制在 10 个以内
10~50 人团队8Gi4g2g常规配置,覆盖面最广
百人以上、多格式仓库16Gi8g4g建议同时加 PVC 快照
超大仓库(TB 级)32Gi16g6g需要关注 GC 停顿

另外,requests.memory不要设得太贴近 limit。设成 limit 的一半左右比较合适,既能保证调度时有足够空间,又不会因为预留过多导致节点装不下别的服务。

有一个反直觉的点:CPU limit 设太低也会导致 OOM 假象。Nexus 启动时如果 CPU 被限流得厉害,JVM 的 GC 线程跑不动,垃圾回收跟不上分配速度,堆会迅速涨满然后 OOM。所以 CPU limit 别设成 0.5 这种数字,给到 2~4 核比较稳妥。我见过一个案例,把 CPU limit 从 0.5 调到 4,OOMKilled 就自己消失了,内存参数一个没改。

6.3 探针:Pod 一直 Running 但 Ready 是 0/1

这个现象最迷惑人,因为 Pod 状态看着挺好,就是不接流量。

先分清楚是哪一类。如果kubectl get pod显示Running但READY是0/1,且RESTARTS是 0,说明容器在跑但 readinessProbe 没通过;如果RESTARTS一直在涨,那是 livenessProbe 在杀它。这两种情况的原因完全不同。

第一种情况的排查路径:先kubectl describe pod看 readinessProbe 的失败事件,通常会带具体错误。如果报connection refused,说明端口没监听——Nexus 还在启动过程中,只是 startupProbe 已经过了(startupProbe 过了之后 readinessProbe 才开始生效,但如果 startupProbe 的 failureThreshold 给得太小,可能在 Nexus 完成初始化之前就误判为“启动成功”)。如果报404,说明路径不对,检查是不是改了NEXUS_CONTEXT。

第二种情况的典型表现是无限重启。这时候要看kubectl logs --previous,但很可能什么都没有——因为容器是被 kubelet 杀的,不是自己崩的。判断方法是看 Pod 的 Events 里有没有Liveness probe failed加Killing container。有的话就是探针超时太短。

我的经验参数是:startupProbe的failureThreshold × periodSeconds总时长给到10 分钟以上(60 × 10 = 600 秒,加上 initialDelay 30 秒,总共 10 分半),livenessProbe的timeoutSeconds给到 10 秒、failureThreshold给到 5,这样单次抖动不会触发重启。readiness 可以稍微激进一点,因为它只影响接不接流量,不影响进程存活。

还有一个细节:健康检查用/service/rest/v1/status比用/好。访问首页会触发一堆模板渲染和静态资源加载,在高负载时可能超时,而 status 接口只是个轻量的服务状态查询。不过要注意某些旧版本的 status 接口行为有差异,落地前先手动 curl 一次确认返回码是 200。

7. 跑起来之后:初始化、备份与升级的现实做法

7.1 首次登录与最小安全加固

Pod Ready 之后,第一件事是拿初始密码:

kubectl exec nexus-0 -n nexus -- cat /nexus-data/admin.password

这个文件里是随机生成的 admin 初始密码。登录后系统会强制你改密码,改完这个文件会被删除。如果发现文件不存在、又登不进去,通常是因为之前初始化过一次、密码已经改过了但没人记录——这种时候只能重置数据目录(如果数据不重要)或者联系之前的管理员。建议在首次部署的 runbook 里明确写上“改完密码立即存入密码管理工具”。

加固这块,至少做三件事。第一,禁用匿名访问,Nexus 默认允许匿名读取,内部仓库如果只是想给团队用,关掉更安全。第二,给 CI 建专用账号并只授最小权限,不要用 admin 跑流水线,一旦凭证泄露影响面太大。第三,把 Realm 顺序调一下,默认的NexusAuthenticatingRealm之外如果接了 LDAP,顺序不对会导致本地账号登不进去。

还有一点容易被忽略:Nexus 的 REST API 里有些管理接口默认是可以被有读权限的账号调用的,做权限规划时要把 API 权限和 UI 权限一起考虑,别只想着“他能看见就行”。

7.2 备份:运行时直接打包数据目录是错误的做法

我见过不止一次有人在 Pod 里tar -czf backup.tar.gz /nexus-data然后拷出来当备份。这个操作在 Nexus 运行期间做,拿到的很可能是不一致的数据——数据库文件正在被写入,打包时抓到的可能是写了一半的状态,恢复的时候直接起不来。

正确做法有三档,按可靠性从高到低:

第一档是 VolumeSnapshot。如果存储类支持 CSI 快照,创建一个 VolumeSnapshot 对象就能拿到块级别的瞬时一致性快照,对业务几乎无感。恢复时从快照建新 PVC 挂上去即可。这是生产环境的首选,前提是你的 CSI 支持。

第二档是 Nexus 内置的 export 任务。在 UI 的 Tasks 里可以创建 “Export databases” 任务,它会在服务运行状态下导出一份逻辑备份。注意这导出的是数据库和配置,blob store 里的构件本体还得单独处理——通常是配合 blob store 的 rsync 或者对象存储版本控制来做。

第三档是停服冷备。把 StatefulSet 缩到 0,等 Pod 完全终止后,用一个临时的 Pod 挂载同一块 PVC 去打包,打完再恢复副本数。这种方式最笨但最可靠,适合没有快照能力的中小环境,代价是有一段时间不可用。

不管用哪一档,都要做恢复演练。备份文件躺在那里从来不验证,等于没有备份。我会建议每季度挑一个非高峰时段,把备份恢复到另一个命名空间里,确认能正常启动、能拉到构件,再删掉。这一步花不了多少时间,但能在真出事的时候救你一命。

7.3 升级的边界在哪里

StatefulSet 的默认更新策略RollingUpdate在单副本场景下就是“删旧建新”,中间会短暂中断。对内部仓库来说这个可以接受,但要提前通知使用者,别赶在月末发布日升级。

升级流程我的习惯是这样:先看一眼目标版本的 upgrade notes,确认有没有“必须先升到某个中间版本”的要求。Nexus 3 的大部分小版本可以直接跨,但涉及存储引擎变更的版本会有前置条件,跳过中间版本可能导致元数据迁移失败。这一步偷懒的代价可能是几个小时的恢复时间。

然后做一次完整备份,再改镜像 tag。StatefulSet 检测到 Pod 模板变化后会自动重建 nexus-0。这时候盯紧 startupProbe 和日志,首次启动可能会做数据迁移,耗时比平时长,别因为看着慢就手动去删 Pod。

回滚的边界要清楚:镜像可以回滚,数据不能。如果新版本已经完成了元数据迁移,把镜像切回旧版本很可能会因为数据格式不认识而起不来。所以升级前那次备份,是唯一的退路。反过来说,如果你确认了新版本运行正常,那备份可以保留一段时间再清理,别急着删。

最后分享一个我自己用了挺久的小习惯:给 Nexus 的 StatefulSet 加一个kubectl annotate记录当前版本和升级时间,比如nexus.io/deployed-version: "3.65.0"。半年后回头看,一眼就知道这个环境什么时候动过、从哪个版本来的,比翻 CI 记录快得多。这类元信息看着琐碎,但在多环境多集群的场景下,能省掉大量“这个集群到底跑的什么版本”的确认成本。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 5:09:21

DevDay 2026深度拆解:Personal Agent常驻代理与Sol 6.1工具调用实战

1. 这场发布会到底讲了什么&#xff1a;从标题拆解核心信息先把标题里的三个关键词拆开看&#xff1a;Personal Agent、Sol 6.1、缺席的Astra。这三个词基本就是整场DevDay 2026的信息骨架——一个正式发布、一个版本迭代、一个被反复追问却没上台的东西。Personal Agent是这次…

作者头像 李华
网站建设 2026/10/26 5:09:07

区块链与iPaaS融合:企业系统集成的信任革新

1. 当区块链遇上iPaaS&#xff1a;系统集成的信任革命企业级系统集成领域正在经历一场由区块链技术引发的底层架构变革。作为某跨国企业数字化转型项目的技术负责人&#xff0c;我在过去三年主导了七个基于区块链的iPaaS&#xff08;Integration Platform as a Service&#xf…

作者头像 李华
网站建设 2026/10/26 5:09:06

Spring Boot接口文档自动生成:springdoc-openapi入门

简介&#xff1a;围绕Spring Boot与OpenAPI 3文档化实践整理的示例工程合集&#xff0c;面向具备Java基础、需要为REST接口生成规范说明和交互式文档的后端开发者。压缩包共219个文件&#xff0c;含139个Java源码、23个XML配置、21个YAML文件及6个Markdown说明&#xff0c;整体…

作者头像 李华
网站建设 2026/10/26 5:08:47

RAG知识库问答翻车实录:为什么向量检索总找到“最像但不对”的答案

1. 问题到底出在哪&#xff1a;从一个真实翻车现场说起去年帮一个做工业设备售后支持的团队调他们的知识库问答系统&#xff0c;他们内部叫“小工单助手”&#xff0c;底层是RAG那一套&#xff1a;文档切片、向量化、检索、拼上下文、丢给大模型生成。上线头两周反馈还不错&…

作者头像 李华
网站建设 2026/10/26 5:08:12

PyTorch神经网络训练全链路实战:从数据管道到模型导出

1. 动手之前先把整条链路的骨架搭出来很多人在 PyTorch 上写神经网络代码&#xff0c;卡住的地方几乎都不在模型结构上&#xff0c;反而卡在训练循环里几行看起来不起眼的代码&#xff1a;忘了zero_grad、验证时忘了model.eval()、标签张量的 dtype 给成了 float、验证集指标比…

作者头像 李华
网站建设 2026/10/26 5:07:48

港口能源系统优化:建模、求解与仿真实践

1. 港口综合能源系统的现实挑战与优化需求现代港口作为全球贸易的关键节点&#xff0c;其能源消耗占全球物流体系的17%以上。我曾在某大型集装箱码头亲眼目睹过这样的场景&#xff1a;三艘万吨级货轮同时靠港&#xff0c;岸桥、龙门吊、冷链仓储全速运转&#xff0c;瞬间用电负…

作者头像 李华