| 项目 | 配置 |
|---|---|
| Kubernetes | v1.32.5,单节点 k8s |
| 节点 IP | 10.12.9.6 |
| Harbor Chart / App | 1.19.2 / 2.15.2 |
| 域名 | harbor.******.com |
| 访问地址 | https://harbor.******.com:31443 |
| Ingress | nginx IngressClass,NodePort HTTPS 31443 |
| 本地数据目录 | /data/harbor |
| 存储类 | harbor-local |
本部署容量为测试用途。生产环境应使用独立数据盘并根据镜像量规划容量和备份。
若未安装 Helm:
添加官方 Harbor Chart 仓库:
选择 harbor/harbor Chart 1.19.2,对应 Harbor 2.15.2。
创建 /root/harbor/manifests/00-storageclass-namespace.yaml:
Retain 表示删除 PVC 或 Helm Release 时不自动删除数据;WaitForFirstConsumer 确保 Local PV 只会在匹配节点调度后使用。
测试容量:Registry 20Gi、Database 10Gi、Redis 2Gi、Jobservice 2Gi、Trivy 10Gi。0777 仅用于测试,生产环境应收紧目录权限。
创建 /root/harbor/manifests/01-pv.yaml。所有 PV 都使用 harbor-local、Retain、ReadWriteOnce,并固定至节点 k8s。
kubectl apply --dry-run=server -f /root/harbor/manifests/01-pv.yaml
kubectl apply -f /root/harbor/manifests/01-pv.yaml创建 /root/harbor/manifests/02-pvc.yaml:
在创建 Harbor 前,5 个 PVC 与 PV 均必须为 Bound。
Ingress NGINX 当前以 NodePort 对外暴露:HTTP 31080、HTTPS 31443。DNS 应配置:
harbor.******.com. A 10.12.9.6getent hosts harbor.******.com
curl -kI https://harbor.使用真实证书和完整证书链创建 TLS Secret:
证书 SAN 必须包含 harbor.******.com。如使用防火墙,放行 31443/tcp。
创建 /root/harbor/values-test.yaml,使用安全输入避免密码直接出现在 Shell 历史中:
预检无误后安装:
浏览器打开 https://harbor.******.com:31443,使用 admin 和安装时设置的密码登录。创建测试项目后,可执行:
适用环境:Kubernetes 单节点、Harbor Helm Chart 1.19.2、Harbor 2.15.2、命名空间 harbor。
本手册包含不可逆的数据删除操作。执行“彻底清理”前,必须确认镜像、数据库和备份不再需要。
记录以下信息:Helm Release 名称、Harbor 命名空间、5 个 PVC/PV 名称、TLS Secret 名称和宿主机数据根目录。
当前部署默认值:
Release: harbor
Namespace: harbor
TLS Secret: harbor-tls
StorageClass: harbor-local
Data root: /data/harbor应使用 Harbor 管理接口或停机后复制 /data/harbor/registry。示例:
sudo rsync -aHAX /data/harbor/registry/ /backup/harbor/registry/卸载前先执行数据库逻辑备份:
请将备份目录复制到独立磁盘或备份系统,不能只保存在 /data/harbor。
适用于升级、重装或暂时停用 Harbor。该流程不删除镜像、数据库和 PVC。
helm uninstall harbor -n harborkubectl get deploy,sts,svc,ingress -n harbor
helm list -n harbor预期:Helm Release 不存在;业务 Deployment、StatefulSet、Service 和 Ingress 已删除。
由于 Helm values 设置了 persistence.resourcePolicy: keep,且 PV 使用 Retain,数据应继续保留。
仅在确认数据已备份且不再需要时执行。
helm uninstall harbor -n harbor等待工作负载消失:
kubectl wait --for=delete pod --all验证 PVC 已不存在:
kubectl get pvc -n harbor只删除以下已确认属于 Harbor 的 PV:
验证:
kubectl
不要删除 kube-root-ca.crt 或由 Istio 自动维护的 istio-ca-root-cert,除非确认它们不是系统自动资源。
kubectl delete namespace harbor
kubectl wait --for=delete namespace/harbor --timeout=180s若 Namespace 长时间为 Terminating,先盘点残留资源:
只有确认 Namespace 内无资源后,才可处理残留终结器。禁止对仍有业务资源的 Namespace 直接强制 finalize。
harbor-local 可能被其他 Harbor 或业务 PVC 使用。先确认引用:
kubectl get pv -o custom-columns=NAME:.metadata.name,SC:.spec.storageClassName,CLAIM:.spec.claimRef.namespace/.spec.claimRef.name | grep harbor-local确认没有任何 PV/PVC 使用后再删除:
kubectl delete storageclass harbor-local如果仍有其他卷使用,不要删除 StorageClass。
这是不可逆操作,仅在确认备份可恢复后执行。
先核对路径:
readlink -f /data/harbor
find清空 Harbor 数据目录内容:
确认目录为空:
sudo du -sh /data/harbor/*如需连目录一起删除:
sudo rmdir /data/harbor/registry /data/harbor/database /data/harbor/redis /data/harbor/jobservice /data/harbor/trivy不要对 /data 或根目录执行递归删除。
彻底清理完成的标准:
harbor Namespace 不存在。/data/harbor 组件目录为空或已删除。保留数据重装时,不要删除 PVC/PV 和 /data/harbor 内容;使用相同 PVC 名称和相同数据库密码。
彻底清理后重装时,应重新创建 StorageClass、Namespace、目录、PV、PVC、TLS Secret 和 Helm values,再执行:
kubectl get nodes -o wide
kubectl get ingressclass
kubectl get svc -n ingress-nginx -o wide
kubectl get storageclass
helm versioncurl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 -o /tmp/get-helm-3
sudo chmod 700 /tmp/get-helm-3
sudo /tmp/get-helm-3
export PATH=/usr/local/bin:$PATH
echo 'export PATH=/usr/local/bin:$PATH' | sudo tee /etc/profile.d/helm.sh
sudo chmod 644 /etc/profile.d/helm.sh
helm versionhelm repo add harbor https://helm.goharbor.io
helm repo update
helm search repo harbor/harbor --versions | head -10apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: harbor-local
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: false
---
apiVersion: v1
kind: Namespace
metadata:
name: harbor
labels:
app.kubernetes.io/part-of: harbor
istio-injection: disabled
pod-security.kubernetes.io/enforce: baselinemkdir -p /root/harbor/manifests
kubectl apply -f /root/harbor/manifests/00-storageclass-namespace.yaml
kubectl create serviceaccount default -n harbor --dry-run=client -o yaml | kubectl apply -f -
kubectl get storageclass harbor-local
kubectl get ns harborsudo install -d -m 0777 \
/data/harbor/registry \
/data/harbor/database \
/data/harbor/redis \
/data/harbor/jobservice \
/data/harbor/trivy
df -hT /data/harbor
stat -c '%A %U:%G %n' /data/harbor/{registry,database,redis,jobservice,trivy}apiVersion: v1
kind: PersistentVolume
metadata:
name: harbor-registry-pv
spec:
capacity: {storage: 20Gi}
accessModes: [ReadWriteOnce]
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Retain
storageClassName: harbor-local
local: {path: /data/harbor/registry}
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [k8s]
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: harbor-database-pv
spec:
capacity: {storage: 10Gi}
accessModes: [ReadWriteOnce]
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Retain
storageClassName: harbor-local
local: {path: /data/harbor/database}
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [k8s]
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: harbor-redis-pv
spec:
capacity: {storage: 2Gi}
accessModes: [ReadWriteOnce]
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Retain
storageClassName: harbor-local
local: {path: /data/harbor/redis}
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [k8s]
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: harbor-jobservice-pv
spec:
capacity: {storage: 2Gi}
accessModes: [ReadWriteOnce]
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Retain
storageClassName: harbor-local
local: {path: /data/harbor/jobservice}
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [k8s]
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: harbor-trivy-pv
spec:
capacity: {storage: 10Gi}
accessModes: [ReadWriteOnce]
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Retain
storageClassName: harbor-local
local: {path: /data/harbor/trivy}
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [k8s]apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: registry-data, namespace: harbor}
spec:
accessModes: [ReadWriteOnce]
storageClassName: harbor-local
volumeName: harbor-registry-pv
resources: {requests: {storage: 20Gi}}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: database-data, namespace: harbor}
spec:
accessModes: [ReadWriteOnce]
storageClassName: harbor-local
volumeName: harbor-database-pv
resources: {requests: {storage: 10Gi}}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: redis-data, namespace: harbor}
spec:
accessModes: [ReadWriteOnce]
storageClassName: harbor-local
volumeName: harbor-redis-pv
resources: {requests: {storage: 2Gi}}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: jobservice-data, namespace: harbor}
spec:
accessModes: [ReadWriteOnce]
storageClassName: harbor-local
volumeName: harbor-jobservice-pv
resources: {requests: {storage: 2Gi}}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: trivy-data, namespace: harbor}
spec:
accessModes: [ReadWriteOnce]
storageClassName: harbor-local
volumeName: harbor-trivy-pv
resources: {requests: {storage: 10Gi}}kubectl apply --dry-run=server -f /root/harbor/manifests/02-pvc.yaml
kubectl apply -f /root/harbor/manifests/02-pvc.yaml
kubectl get pvc -n harbor
kubectl get pv | grep '^harbor-'kubectl create secret tls harbor-tls \
-n harbor \
--cert=/path/to/fullchain.pem \
--key=/path/to/privkey.key
kubectl get secret harbor-tls -n harborread -rsp '请输入 Harbor 管理员密码: ' ADMIN_PASSWORD
echo
cat > /root/harbor/values-test.yaml <<EOF
expose:
type: ingress
tls:
enabled: true
certSource: secret
secret:
secretName: harbor-tls
ingress:
hosts:
core: harbor.******.com
className: nginx
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
externalURL: https://harbor.******.com:31443
harborAdminPassword: "${ADMIN_PASSWORD}"
persistence:
enabled: true
resourcePolicy: keep
persistentVolumeClaim:
registry: {existingClaim: registry-data}
jobservice:
jobLog: {existingClaim: jobservice-data}
database: {existingClaim: database-data}
redis: {existingClaim: redis-data}
trivy: {existingClaim: trivy-data}
trivy:
enabled: true
metrics:
enabled: true
EOF
unset ADMIN_PASSWORD
chmod 600 /root/harbor/values-test.yamlhelm template harbor harbor/harbor \
--namespace harbor \
--version 1.19.2 \
-f /root/harbor/values-test.yaml \
> /root/harbor/rendered.yaml
kubectl apply --dry-run=server -f /root/harbor/rendered.yamlhelm install harbor harbor/harbor \
--namespace harbor \
--version 1.19.2 \
-f /root/harbor/values-test.yaml \
--wait \
--timeout 10mhelm status harbor -n harbor
kubectl get pods -n harbor -o wide
kubectl get pvc -n harbor
kubectl get ingress -n harbor
kubectl get events -n harbor --sort-by='.lastTimestamp' | tail -40
curl -kI https://harbor.******.com:31443docker login harbor.******.com:31443
docker pull nginx:alpine
docker tag nginx:alpine harbor.******.com:31443/library/nginx:alpine
docker push harbor.******.com:31443/library/nginx:alpine
docker image rm harbor.******.com:31443/library/nginx:alpine
docker pull harbor.******.com:31443/library/nginx:alpinehelm list -n harbor
kubectl get all,pvc,secret,configmap,ingress -n harbor
kubectl get pv | grep -E 'harbor-(registry|database|redis|jobservice|trivy)-pv'BACKUP_DIR=/root/harbor/backup/$(date +%F-%H%M%S)
mkdir -p "$BACKUP_DIR"
helm get values harbor -n harbor > "$BACKUP_DIR/values.yaml"
helm get manifest harbor -n harbor > "$BACKUP_DIR/manifest.yaml"
kubectl get pvc -n harbor -o yaml > "$BACKUP_DIR/pvc.yaml"
kubectl get pv -o yaml > "$BACKUP_DIR/all-pv.yaml"
kubectl get secret harbor-tls -n harbor -o yaml > "$BACKUP_DIR/harbor-tls-secret.yaml"kubectl exec -n harbor statefulset/harbor-database -- \
sh -c 'pg_dump -U postgres -d registry' \
> "$BACKUP_DIR/registry.sql"kubectl get pvc -n harbor
kubectl get pv | grep -E 'harbor-(registry|database|redis|jobservice|trivy)-pv'
sudo du -sh /data/harbor/*kubectl delete pvc \
registry-data \
database-data \
redis-data \
jobservice-data \
trivy-data \
-n harborkubectl delete pv \
harbor-registry-pv \
harbor-database-pv \
harbor-redis-pv \
harbor-jobservice-pv \
harbor-trivy-pvkubectl api-resources --verbs=list --namespaced -o name |
while read -r resource; do
kubectl get -n harbor "$resource" -o name --ignore-not-found 2>/dev/null
donesudo find /data/harbor/registry -mindepth 1 -delete
sudo find /data/harbor/database -mindepth 1 -delete
sudo find /data/harbor/redis -mindepth 1 -delete
sudo find /data/harbor/jobservice -mindepth 1 -delete
sudo find /data/harbor/trivy -mindepth 1 -deletehelm list -A | grep -i harbor || true
kubectl get ns harbor 2>/dev/null || echo 'harbor Namespace 已删除'
kubectl get pv | grep -i harbor || true
kubectl get pvc -A | grep -i harbor || true
kubectl get ingress -A | grep -i harbor || true
sudo find /data/harbor -mindepth 1 -print 2>/dev/nullhelm install harbor harbor/harbor \
--namespace harbor \
--version 1.19.2 \
-f /root/harbor/values-test.yaml \
--wait \
--timeout 10m