这篇文章只讨论新建 Nexus Repository 3,不包含 Nexus 2 数据迁移。先把一套干净的 Nexus 3 跑稳定,再进入下一篇迁移教程,排障边界会更清晰。
系列导航:Kubernetes 重建迁移教程系列。本篇是 Nexus 迁移的前置教程。
示例使用单实例 StatefulSet、外部 PostgreSQL 和持久卷。高可用多实例涉及授权与官方 HA 架构,不能简单把 replicas 从 1 改成 3。
最终结构
flowchart LR
A["Maven / npm / Docker 客户端"] --> B["Ingress 或内部 VIP"]
B --> C["nexus Service"]
C --> D["Nexus StatefulSet 单实例"]
D --> E["PVC /nexus-data"]
D --> F["PostgreSQL"]
G["备份任务"] --> E
G --> F
Nexus 官方容器把配置、日志和制品数据放在 /nexus-data,容器进程使用 UID 200。这个目录必须持久化并可写。参考官方容器镜像说明。
第 1 步:确定版本、容量和上下文路径
先冻结以下值:
NEXUS_VERSION=<approved-version>
NEXUS_IMAGE=<image-registry>/sonatype/nexus3:${NEXUS_VERSION}
NEXUS_HOST=repo.example.com
NEXUS_CONTEXT=/nexus
不要使用 latest。把镜像同步到内部仓库后记录 digest:
crane digest "$NEXUS_IMAGE"
容量至少考虑:
- 当前 hosted 制品总量。
- proxy cache 的上限和清理策略。
- 数据库、日志、任务临时空间。
- 备份保留周期。
- 未来一到两年的增长量。
第 2 步:创建命名空间和 PostgreSQL 数据库
kubectl create namespace repository-system
PostgreSQL 建议由独立数据库平台提供并完成备份。创建专用账号和数据库:
CREATE ROLE nexus LOGIN PASSWORD '<generated-password>';
CREATE DATABASE nexus OWNER nexus ENCODING 'UTF8';
验证 Nexus 节点能连接数据库:
kubectl -n repository-system run pg-check --rm -it --restart=Never \
--image=<approved-postgres-client-image> -- \
psql 'postgresql://nexus:<password>@<postgres-host>:5432/nexus' \
-c 'select current_database(), current_user;'
密码不要出现在 shell history 中。正式环境使用交互输入、临时环境变量或 Secret 管理系统。
第 3 步:创建数据库 Secret
kubectl -n repository-system create secret generic nexus-database \
--from-literal=jdbc-url='jdbc:postgresql://<postgres-host>:5432/nexus' \
--from-literal=username='nexus' \
--from-literal=password='<generated-password>'
官方支持用环境变量、JVM 参数或属性文件配置 PostgreSQL,但不建议混用。容器环境中直接使用环境变量最清晰。参考使用 PostgreSQL 安装 Nexus Repository。
第 4 步:创建 PVC
保存为 00-storage.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nexus-data
namespace: repository-system
spec:
storageClassName: <storage-class>
accessModes: [ReadWriteOnce]
resources:
requests:
storage: <planned-size>
应用并确认绑定:
kubectl apply -f 00-storage.yaml
kubectl -n repository-system get pvc nexus-data -w
PVC 必须显示 Bound。如果是静态本地卷,还要给 Nexus Pod 添加 nodeAffinity,并把节点故障恢复写进 Runbook。
第 5 步:创建 Service
保存为 01-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: nexus
namespace: repository-system
spec:
selector:
app.kubernetes.io/name: nexus
ports:
- name: http
port: 8081
targetPort: http
kubectl apply -f 01-service.yaml
后续若为 Docker hosted/group 仓库分配独立端口,应在 Service 和容器端口中显式添加,不要与管理端口混淆。
第 6 步:创建 StatefulSet
保存为 02-statefulset.yaml:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nexus
namespace: repository-system
spec:
serviceName: nexus
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: nexus
template:
metadata:
labels:
app.kubernetes.io/name: nexus
spec:
terminationGracePeriodSeconds: 120
securityContext:
fsGroup: 200
fsGroupChangePolicy: OnRootMismatch
containers:
- name: nexus
image: <image-registry>/sonatype/nexus3:<approved-version>@sha256:<digest>
ports:
- { name: http, containerPort: 8081 }
env:
- { name: NEXUS_CONTEXT, value: /nexus }
- name: NEXUS_DATASTORE_NEXUS_JDBCURL
valueFrom:
{ secretKeyRef: { name: nexus-database, key: jdbc-url } }
- name: NEXUS_DATASTORE_NEXUS_USERNAME
valueFrom:
{ secretKeyRef: { name: nexus-database, key: username } }
- name: NEXUS_DATASTORE_NEXUS_PASSWORD
valueFrom:
{ secretKeyRef: { name: nexus-database, key: password } }
- name: INSTALL4J_ADD_VM_PARAMS
value: >-
-Xms2g -Xmx2g -XX:MaxDirectMemorySize=2g
-Djava.util.prefs.userRoot=/nexus-data/javaprefs
resources:
requests: { cpu: "2", memory: 4Gi }
limits: { cpu: "4", memory: 6Gi }
startupProbe:
httpGet: { path: /nexus/service/rest/v1/status, port: http }
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 60
readinessProbe:
httpGet: { path: /nexus/service/rest/v1/status, port: http }
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
livenessProbe:
httpGet: { path: /nexus/service/rest/v1/status, port: http }
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 6
volumeMounts:
- { name: data, mountPath: /nexus-data }
volumes:
- name: data
persistentVolumeClaim: { claimName: nexus-data }
资源只是起点,应结合索引规模、并发请求和官方系统要求压测后再调整。不要把 JVM 堆直接设置成容器内存上限。
应用前验证:
kubectl apply --server-side --dry-run=server -f 02-statefulset.yaml
kubectl apply -f 02-statefulset.yaml
kubectl -n repository-system rollout status statefulset/nexus --timeout=20m
首次启动可能需要数分钟。startupProbe 应覆盖数据库初始化和插件加载时间,readinessProbe 不应过早放行流量。
第 7 步:排查启动失败
kubectl -n repository-system get pod nexus-0 -o wide
kubectl -n repository-system describe pod nexus-0
kubectl -n repository-system logs nexus-0 --tail=300
kubectl -n repository-system get endpointslice \
-l kubernetes.io/service-name=nexus -o wide
常见原因:
- PVC 未绑定,或目录对 UID 200 不可写。
- PostgreSQL 地址、账号或 TLS 参数错误。
- 探针路径没有包含
/nexus上下文。 - 容器内存太小导致 OOMKilled。
- 数据库 schema 已被不兼容版本初始化。
connection refused 表示端口尚未监听,首次启动期间应看应用日志,而不是不停删除 Pod。
第 8 步:读取首次管理员密码
kubectl -n repository-system exec nexus-0 -- \
sh -c 'test -f /nexus-data/admin.password && cat /nexus-data/admin.password'
第一次登录后:
- 立即修改管理员密码并保存到密码管理器。
- 创建个人管理员账号,避免共享
admin。 - 根据安全要求决定是否允许匿名下载。
- 配置邮件、审计和清理任务。
- 确认
admin.password文件已被 Nexus 删除或失效。
不要把初始密码写进 Wiki、Git 或聊天记录。
第 9 步:创建 Ingress
保存为 03-ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nexus
namespace: repository-system
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"
spec:
ingressClassName: nginx
tls:
- hosts: [repo.example.com]
secretName: repo-example-com-tls
rules:
- host: repo.example.com
http:
paths:
- path: /nexus
pathType: Prefix
backend:
service:
name: nexus
port: { number: 8081 }
先不改公共 DNS,用 --resolve 测试:
curl -kfsS --resolve repo.example.com:443:<ingress-address> \
https://repo.example.com/nexus/service/rest/v1/status
预期返回可用状态。上传大制品前,再验证 Ingress、负载均衡器和 CDN 都没有请求体限制。
第 10 步:创建 Maven 仓库并验证
在 UI 创建:
maven-releases:hosted,Version policy 为 Release。maven-snapshots:hosted,Version policy 为 Snapshot。maven-central:proxy,远端为 Maven Central。maven-public:group,包含以上仓库。
客户端仓库地址:
<repository>
<id>nexus-public</id>
<url>https://repo.example.com/nexus/repository/maven-public/</url>
</repository>
发布地址:
<distributionManagement>
<repository>
<id>nexus-releases</id>
<url>https://repo.example.com/nexus/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>nexus-snapshots</id>
<url>https://repo.example.com/nexus/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
凭据放在用户 settings.xml 或 CI Secret 中,不要写进项目 POM。
使用一个临时 Maven 项目执行:
mvn -U dependency:get -Dartifact=<group>:<artifact>:<version>
mvn deploy
第 11 步:配置备份与清理任务
备份必须同时覆盖:
- PostgreSQL 一致性备份。
/nexus-data中的 blob、配置和密钥材料。- Kubernetes 清单与 Secret 管理系统中的引用。
数据库与 blob 的备份时间点要协调,否则元数据和文件可能不一致。proxy cache 可设置清理策略,但 hosted 制品删除必须有保留和审计规则。
完成定义
- StatefulSet 重建后使用同一 PVC 正常恢复。
- PostgreSQL 和
/nexus-data均完成恢复演练。 - Service、Ingress、TLS 和上下文路径一致。
- Maven 下载、Release 发布、Snapshot 发布均成功。
- 管理员初始密码已轮换,CI 使用独立最小权限账号。
- 镜像版本和 digest 已固定,升级回滚步骤已记录。
完成干净部署后,再进入 Nexus 2 到 Nexus 3 的数据迁移,不要把“新平台搭建”和“历史数据导入”混成一次不可回退的操作。
DISCUSSION
讨论与反馈