Chemmy's Blog

chengming0916@outlook.com

一、核心定位

本文作为GitOps环境搭建系列的第五篇,聚焦基础组件(数据库和缓存)的部署与配置。在GitOps环境中,数据库和缓存是支撑核心组件(Gitea、ArgoCD、Tekton、Harbor等)运行的关键基础设施。

基础组件包括:

  • PostgreSQL/MySQL:关系型数据库,用于存储Gitea、Harbor等组件的元数据
  • Redis:内存缓存,用于提升组件性能和会话管理

本文采用独立命名空间规划,将PostgreSQL部署在postgres命名空间,Redis部署在redis命名空间,MySQL作为备选方案部署在mysql命名空间。所有配置均适配K3s轻量级环境,使用local-path存储类保障数据持久化,并通过Traefik的IngressRouteTCP实现服务访问。

二、部署前置检查

部署前需验证K3s集群状态及存储配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 验证K3s集群状态
kubectl get nodes

# 2. 验证存储类配置
kubectl get storageclass
kubectl describe storageclass local-path

# 3. 验证Traefik运行状态
kubectl get pods -n kube-system -l app=traefik

# 4. 检查命名空间
kubectl get ns postgres redis mysql 2>/dev/null || echo "命名空间不存在,将自动创建"

# 5. 验证网络连通性
ping -c 3 8.8.8.8

前置条件检查清单:

  • K3s集群运行正常
  • local-path存储类可用
  • Traefik Pod处于Running状态
  • 网络可访问外网(用于拉取镜像)
  • 具备集群管理权限

三、创建独立命名空间

为实现数据库与缓存服务的资源隔离和精细化管理,分别创建专属命名空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# namespaces.yaml
apiVersion: v1
kind: Namespace
metadata:
name: postgres
---
apiVersion: v1
kind: Namespace
metadata:
name: redis
---
apiVersion: v1
kind: Namespace
metadata:
name: mysql

应用配置:

1
2
3
4
kubectl apply -f namespaces.yaml

# 验证命名空间创建
kubectl get namespaces | grep -E "postgres|redis|mysql"

四、部署PostgreSQL

4.1 配置PostgreSQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# postgresql.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
namespace: postgres
data:
POSTGRES_DB: "gitea"
POSTGRES_USER: "gitea"
POSTGRES_PASSWORD: "gitea123"
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C"
PGDATA: "/var/lib/postgresql/data/pgdata"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: postgres
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: postgres
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
envFrom:
- configMapRef:
name: postgres-config
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: postgres
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
type: ClusterIP

4.2 配置IngressRouteTCP(可选)

如需通过Traefik外部访问PostgreSQL,配置IngressRouteTCP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# postgres-ingressroute-tcp.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: postgres-ingressroute-tcp
namespace: postgres
spec:
entryPoints:
- postgres-tcp # 需在Traefik中配置此TCP入口点
routes:
- match: HostSNI(`*`)
services:
- name: postgres
port: 5432

4.3 应用PostgreSQL配置

1
2
3
4
5
6
7
8
9
10
# 部署PostgreSQL
kubectl apply -f postgresql.yaml

# 可选:部署IngressRouteTCP
kubectl apply -f postgres-ingressroute-tcp.yaml

# 验证部署状态
kubectl get pods -n postgres
kubectl get pvc -n postgres
kubectl get svc -n postgres

五、部署Redis

5.1 配置Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# redis.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-config
namespace: redis
data:
redis.conf: |
bind 0.0.0.0
protected-mode no
port 6379
requirepass "redis123456"
appendonly yes
appendfsync everysec
dir /data
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: redis
spec:
serviceName: redis
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
command: ["redis-server", "/etc/redis/redis.conf"]
ports:
- containerPort: 6379
volumeMounts:
- name: redis-config
mountPath: /etc/redis
- name: redis-data
mountPath: /data
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "local-path"
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: redis
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
type: ClusterIP

5.2 配置IngressRouteTCP(可选)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# redis-ingressroute-tcp.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: redis-ingressroute-tcp
namespace: redis
spec:
entryPoints:
- redis-tcp # 需在Traefik中配置此TCP入口点
routes:
- match: HostSNI(`*`)
services:
- name: redis
port: 6379

5.3 应用Redis配置

1
2
3
4
5
6
7
8
9
# 部署Redis
kubectl apply -f redis.yaml

# 可选:部署IngressRouteTCP
kubectl apply -f redis-ingressroute-tcp.yaml

# 验证Redis运行状态
kubectl get pods -n redis
kubectl exec -it redis-0 -n redis -- redis-cli -a redis123456 ping

六、备选方案:部署MySQL

6.1 配置MySQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# mysql.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
namespace: mysql
data:
my.cnf: |
[mysqld]
default_authentication_plugin=mysql_native_password
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
---
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
namespace: mysql
type: Opaque
data:
mysql-root-password: cm9vdDEyMzQ1Ng== # root123456 base64编码
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: mysql
spec:
serviceName: mysql
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: mysql-root-password
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "local-path"
resources:
requests:
storage: 20Gi
---
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: mysql
spec:
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
type: ClusterIP

6.2 配置IngressRouteTCP(可选)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# mysql-ingressroute-tcp.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: mysql-ingressroute-tcp
namespace: mysql
spec:
entryPoints:
- mysql-tcp # 需在Traefik中配置此TCP入口点
routes:
- match: HostSNI(`*`)
services:
- name: mysql
port: 3306

6.3 应用MySQL配置

1
2
3
4
5
6
7
8
# 部署MySQL(备选方案)
kubectl apply -f mysql.yaml

# 可选:部署IngressRouteTCP
kubectl apply -f mysql-ingressroute-tcp.yaml

# 验证MySQL运行状态
kubectl get pods -n mysql

七、验证部署结果

7.1 验证组件状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看所有基础组件状态
kubectl get pods -n postgres
kubectl get pods -n redis
kubectl get pods -n mysql 2>/dev/null || echo "MySQL未部署"

# 查看服务状态
kubectl get svc -n postgres
kubectl get svc -n redis
kubectl get svc -n mysql 2>/dev/null || echo "MySQL未部署"

# 查看持久化存储
kubectl get pvc -n postgres
kubectl get pvc -n redis
kubectl get pvc -n mysql 2>/dev/null || echo "MySQL未部署"

7.2 测试连接功能

1
2
3
4
5
6
7
8
9
10
11
# 测试PostgreSQL连接
kubectl run postgres-test --rm -it --image=postgres:15-alpine -n postgres -- \
psql -h postgres.postgres.svc.cluster.local -U gitea -d gitea -c "SELECT 1;"

# 测试Redis连接
kubectl run redis-test --rm -it --image=redis:7-alpine -n redis -- \
redis-cli -h redis.redis.svc.cluster.local -a redis123456 ping

# 测试MySQL连接(如部署)
kubectl run mysql-test --rm -it --image=mysql:8.0 -n mysql -- \
mysql -h mysql.mysql.svc.cluster.local -uroot -proot123456 -e "SELECT 1;"

7.3 验证IngressRouteTCP(如配置)

1
2
3
4
# 查看IngressRouteTCP状态
kubectl get ingressroutetcps.traefik.containo.us -n postgres 2>/dev/null || echo "未配置"
kubectl get ingressroutetcps.traefik.containo.us -n redis 2>/dev/null || echo "未配置"
kubectl get ingressroutetcps.traefik.containo.us -n mysql 2>/dev/null || echo "未配置"

八、服务访问方式

8.1 集群内访问

  • PostgreSQL: postgres.postgres.svc.cluster.local:5432
  • Redis: redis.redis.svc.cluster.local:6379
  • MySQL: mysql.mysql.svc.cluster.local:3306

8.2 集群外访问(需配置IngressRouteTCP)

  • PostgreSQL: Traefik节点IP:5432(需Traefik配置postgres-tcp入口点)
  • Redis: Traefik节点IP:6379(需Traefik配置redis-tcp入口点)
  • MySQL: Traefik节点IP:3306(需Traefik配置mysql-tcp入口点)

九、日常运维命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看组件日志
kubectl logs -f postgres-0 -n postgres
kubectl logs -f redis-0 -n redis
kubectl logs -f mysql-0 -n mysql 2>/dev/null || echo "MySQL未部署"

# 重启组件
kubectl rollout restart statefulset postgres -n postgres
kubectl rollout restart statefulset redis -n redis
kubectl rollout restart statefulset mysql -n mysql 2>/dev/null || echo "MySQL未部署"

# 数据备份(PostgreSQL示例)
kubectl exec postgres-0 -n postgres -- pg_dump -U gitea gitea > gitea-backup-$(date +%Y%m%d).sql

# 进入容器调试
kubectl exec -it postgres-0 -n postgres -- /bin/bash
kubectl exec -it redis-0 -n redis -- /bin/sh
kubectl exec -it mysql-0 -n mysql -- /bin/bash 2>/dev/null || echo "MySQL未部署"

十、常见问题修复

问题现象 排查方向 修复方案
Pod一直Pending 存储类/资源不足 检查local-path存储类,确认节点有足够资源
数据库连接失败 服务发现/认证 检查Service selector,验证用户名密码
Redis认证失败 密码配置 确认redis.conf中的requirepass配置正确
数据持久化失败 PVC绑定问题 检查PVC状态:kubectl describe pvc -n <namespace>
IngressRouteTCP不生效 Traefik入口点配置 确认Traefik已配置对应TCP入口点

十一、配置参考

所有基础组件配置文件和部署脚本请参考:
https://gitee.com/Chemmy/kube-template/tree/master/devops

该目录包含:

  • PostgreSQL完整配置
  • Redis完整配置
  • MySQL完整配置
  • 生产环境优化配置
  • 备份和恢复脚本

总结

本文完成了GitOps环境基础组件(PostgreSQL、Redis、MySQL)的标准化部署,采用独立命名空间实现资源隔离,使用StatefulSet保障有状态服务的稳定性,通过local-path存储类实现数据持久化。

基础组件部署完成后,GitOps环境已具备完整的数据存储和缓存能力。下一篇文章将部署Gitea代码仓库,为GitOps环境提供代码托管服务。

一、核心定位

本文作为GitOps环境搭建系列的第九篇,聚焦容器镜像仓库Harbor的部署与配置。Harbor是由CNCF孵化而成的企业级开源Registry项目,提供完整的镜像存储、签名、扫描和分发能力。

在GitOps环境中,Harbor扮演”镜像存储中枢”角色,作为GitOps流程的镜像仓库环节,接收来自Tekton构建的镜像,并为ArgoCD提供可信的镜像源。Harbor与Gitea、Tekton、ArgoCD协同工作,形成完整的”代码→构建→镜像→部署”自动化流水线。

二、部署前置检查

部署前需验证K3s集群状态及前序组件运行情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 验证K3s集群状态
kubectl get nodes

# 2. 验证PostgreSQL运行状态
kubectl get pods -n postgres

# 3. 验证Redis运行状态
kubectl get pods -n redis

# 4. 验证Traefik运行状态
kubectl get pods -n kube-system -l app=traefik

# 5. 验证cert-manager运行状态
kubectl get pods -n cert-manager

# 6. 验证域名解析
nslookup harbor.example.io
nslookup notary.example.io

前置条件检查清单:

  • K3s集群运行正常
  • PostgreSQL数据库可用(用于存储Harbor元数据)
  • Redis缓存服务可用(用于Harbor会话缓存)
  • Traefik反向代理可用
  • cert-manager证书管理可用
  • 域名harbor.example.ionotary.example.io已解析至K3s节点IP
  • 已创建Harbor数据库和用户(如未创建,见下文步骤)

三、准备Harbor数据库

3.1 创建Harbor数据库

1
2
3
4
# 在PostgreSQL中创建Harbor数据库
kubectl exec -it postgres-0 -n postgres -- psql -U postgres -c "CREATE DATABASE harbor;"
kubectl exec -it postgres-0 -n postgres -- psql -U postgres -c "CREATE USER harbor WITH PASSWORD 'harbor123';"
kubectl exec -it postgres-0 -n postgres -- psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE harbor TO harbor;"

3.2 创建Harbor命名空间

1
2
# 创建Harbor专属命名空间
kubectl create namespace harbor

四、配置Harbor证书

4.1 创建Harbor证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# harbor-certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: harbor-cert
namespace: harbor
spec:
secretName: harbor-tls-secret
issuerRef:
name: selfsigned-cluster-issuer
kind: ClusterIssuer
commonName: harbor.example.io
dnsNames:
- harbor.example.io
- notary.example.io
duration: 2160h
renewBefore: 360h
privateKey:
algorithm: RSA
size: 2048
usages:
- server auth

4.2 应用证书配置

1
2
3
4
5
6
# 应用证书配置
kubectl apply -f harbor-certificate.yaml

# 验证证书状态
kubectl get certificate -n harbor
kubectl describe certificate harbor-cert -n harbor

五、标准化部署Harbor

5.1 添加Helm仓库

1
2
3
# 添加Harbor官方Helm仓库
helm repo add harbor https://helm.goharbor.io --force-update
helm repo update

5.2 创建Harbor配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# harbor-values.yaml
# 参考:https://gitee.com/Chemmy/kube-template/blob/master/devops/Harbor/harbor-values.yaml

# 全局配置
expose:
type: ingress
tls:
enabled: true
certSource: secret
secret:
secretName: "harbor-tls-secret"
notarySecretName: "harbor-tls-secret"
ingress:
hosts:
core: harbor.example.io
notary: notary.example.io
annotations:
kubernetes.io/ingress.class: "traefik"
traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.entrypoints: websecure

externalURL: https://harbor.example.io

# 管理员密码
harborAdminPassword: "HarborAdmin123!"

# 持久化存储
persistence:
enabled: true
resourcePolicy: "keep"
persistentVolumeClaim:
registry:
storageClass: "local-path"
accessMode: ReadWriteOnce
size: 100Gi
chartmuseum:
storageClass: "local-path"
accessMode: ReadWriteOnce
size: 5Gi
jobservice:
storageClass: "local-path"
accessMode: ReadWriteOnce
size: 5Gi
trivy:
storageClass: "local-path"
accessMode: ReadWriteOnce
size: 5Gi

# 外部数据库配置(使用前序部署的PostgreSQL)
database:
type: external
external:
host: "postgres.postgres.svc.cluster.local"
port: "5432"
username: "harbor"
password: "harbor123"
coreDatabase: "harbor"
clairDatabase: "harbor_clair"
notaryServerDatabase: "harbor_notary_server"
notarySignerDatabase: "harbor_notary_signer"

# 外部Redis配置
redis:
type: external
external:
addr: "redis.redis.svc.cluster.local:6379"
password: "redis123456"

# 启用ChartMuseum
chartmuseum:
enabled: true
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"

# 启用Trivy漏洞扫描
trivy:
enabled: true
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"

# 资源限制
nginx:
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"

portal:
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"

core:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"

jobservice:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"

registry:
registry:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"

notary:
enabled: true
server:
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
signer:
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"

5.3 部署Harbor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 部署Harbor
helm install harbor harbor/harbor \
--namespace harbor \
--version 1.13.2 \
-f harbor-values.yaml

# 或使用在线配置
helm install harbor harbor/harbor \
--namespace harbor \
--version 1.13.2 \
--set expose.type=ingress \
--set expose.tls.enabled=true \
--set expose.tls.secret.secretName=harbor-tls-secret \
--set expose.ingress.hosts.core=harbor.example.io \
--set expose.ingress.hosts.notary=notary.example.io \
--set externalURL=https://harbor.example.io \
--set harborAdminPassword=HarborAdmin123! \
--set persistence.enabled=true \
--set persistence.persistentVolumeClaim.registry.storageClass=local-path \
--set persistence.persistentVolumeClaim.registry.size=100Gi \
--set database.type=external \
--set database.external.host=postgres.postgres.svc.cluster.local \
--set database.external.port=5432 \
--set database.external.username=harbor \
--set database.external.password=harbor123 \
--set redis.type=external \
--set redis.external.addr=redis.redis.svc.cluster.local:6379 \
--set redis.external.password=redis123456 \
--set chartmuseum.enabled=true \
--set trivy.enabled=true \
--set notary.enabled=true

六、验证部署结果

6.1 验证组件状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看Harbor Pod状态(首次启动需要2-3分钟)
kubectl get pods -n harbor -w

# 查看Service状态
kubectl get svc -n harbor

# 查看Ingress状态
kubectl get ingress -n harbor

# 查看证书状态
kubectl get certificate -n harbor

# 查看PVC状态
kubectl get pvc -n harbor

6.2 测试访问功能

1
2
3
4
5
6
7
8
9
10
11
# 等待Harbor完全启动
sleep 120

# 测试HTTPS访问
curl -k https://harbor.example.io

# 查看Harbor日志
kubectl logs -n harbor -l app=harbor --tail=50

# 测试Notary访问
curl -k https://notary.example.io

6.3 初始化Harbor

  1. 浏览器访问:https://harbor.example.io
  2. 用户名:admin
  3. 密码:HarborAdmin123!
  4. 首次登录后立即修改密码
  5. 创建项目(如gitops-demo

七、配置容器运行时访问

7.1 Docker客户端配置

1
2
3
4
5
6
7
8
9
10
# 获取Harbor CA证书
kubectl get secret harbor-tls-secret -n harbor -o jsonpath='{.data.ca\.crt}' | base64 --decode > harbor-ca.crt

# 配置Docker信任Harbor证书
sudo mkdir -p /etc/docker/certs.d/harbor.example.io
sudo cp harbor-ca.crt /etc/docker/certs.d/harbor.example.io/ca.crt
sudo systemctl restart docker

# 登录Harbor
docker login harbor.example.io -u admin -p HarborAdmin123!

7.2 Containerd配置(K3s节点)

1
2
3
4
# 配置Containerd信任Harbor证书
sudo mkdir -p /etc/containerd/certs.d/harbor.example.io
sudo cp harbor-ca.crt /etc/containerd/certs.d/harbor.example.io/ca.crt
sudo systemctl restart containerd

7.3 测试镜像推送

1
2
3
4
5
6
7
8
# 拉取测试镜像
docker pull alpine:latest

# 打标签
docker tag alpine:latest harbor.example.io/gitops-demo/alpine:latest

# 推送镜像
docker push harbor.example.io/gitops-demo/alpine:latest

八、Harbor与GitOps全链路集成

完成Harbor部署后,GitOps环境搭建系列已具备完整的端到端交付能力。以下阐述Harbor与系列中其他核心组件的联动关系。

8.1 全链路组件矩阵

组件 职责 在GitOps链路中的角色
Gitea 代码托管与版本控制 开发者推送代码的单一可信数据源
Tekton 云原生CI/CD流水线引擎 监听代码变更,构建镜像并推送至Harbor
Harbor 容器镜像仓库与安全扫描 存储签名镜像,提供可信镜像分发
ArgoCD GitOps持续部署控制平面 监听Harbor镜像Tag变化,自动同步至K3s

8.2 完整流水线联动流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────────────────────────────────┐
│ GitOps 完整链路 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ① 开发者 ──push──▶ Gitea (Git仓库) │
│ │
│ ② Tekton Pipeline ──webhook触发──▶ 克隆代码 │
│ │
│ ③ Tekton Pipeline ──构建──▶ Docker镜像 │
│ │ │
│ │ docker push │
│ ▼ │
│ ④ Harbor (镜像仓库) ◀── 推送镜像 │
│ │ • 存储镜像bits │
│ │ • Trivy CVE扫描 │
│ │ • Notary镜像签名(可选) │
│ │
│ ⑤ ArgoCD Application ──监听镜像Tag变化──▶ 检测到新镜像 │
│ │ │
│ │ 更新Deployment镜像Tag │
│ ▼ │
│ ⑥ K3s集群 ──kubectl apply──▶ 新Pod以新镜像启动 │
│ │
└─────────────────────────────────────────────────────────────────┘

8.3 Harbor与Gitea、Tekton、ArgoCD联动

Harbor + Gitea(认证集成)

Harbor与Gitea可共用外部认证源(如LDAP/OIDC),实现统一身份管理:

1
2
3
4
5
6
7
8
# Harbor配置外部认证
harbor-core:
config:
auth_mode: "oidc_auth"
oidc_name: "Gitea OIDC"
oidc_endpoint: "https://gitea.example.io/oauth"
oidc_client_id: "harbor-client"
oidc_secret: "harbor-oidc-secret"

Harbor + Tekton(镜像构建推送)

Tekton Pipeline执行构建并将镜像推送至Harbor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Tekton Task: 构建并推送镜像至Harbor
spec:
steps:
- name: git-clone
image: alpine/git
script: |
git clone $(params.REPO_URL) $(params.REPO_DIR)

- name: build-image
image: docker:latest
script: |
docker build -t $(params.IMAGE):$(params.TAG) $(params.REPO_DIR)
docker tag $(params.IMAGE):$(params.TAG) \
harbor.example.io/gitops-demo/$(params.IMAGE):$(params.TAG)

- name: push-image
image: docker:latest
script: |
docker login harbor.example.io -u $(params.HARBOR_USER) -p $(params.HARBOR_PASS)
docker push harbor.example.io/gitops-demo/$(params.IMAGE):$(params.TAG)

Harbor + ArgoCD(镜像驱动部署)

ArgoCD Image Updater自动轮询Harbor镜像仓库,发现新Tag后更新配置并触发同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ArgoCD Application(Image Updater注解)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: gitops-demo-app
annotations:
argocd-image-updater.argoproj.io/image-list: |
demo=harbor.example.io/gitops-demo/demo-image
argocd-image-updater.argoproj.io/demo.update-strategy: latest
argocd-image-updater.argoproj.io/write-back-method: git
spec:
source:
repoURL: https://github.com/your-org/gitops-configs.git
path: apps/demo-app
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: gitops-demo

8.4 安全集成要点

  1. 镜像签名链路:Tekton构建 → Notary签名 → 推送Harbor → 部署时验签,全链路可信;
  2. 漏洞扫描卡点:Harbor Trivy扫描未通过的CVE镜像(Critical/High),可配置阻止推送;
  3. 认证打通:Harbor与Gitea共用OIDC认证,实现统一身份管理;
  4. 网络隔离:Harbor仅通过Traefik 443端口暴露,K3s节点通过内部网络拉取镜像。

九、服务访问方式

9.1 集群内访问

  • Web界面harbor-core.harbor.svc.cluster.local
  • Registry APIharbor-registry.harbor.svc.cluster.local
  • ChartMuseumharbor-chartmuseum.harbor.svc.cluster.local

9.2 集群外访问

  • Web界面https://harbor.example.io
  • Registryharbor.example.io(Docker push/pull)
  • Notaryhttps://notary.example.io

十、日常运维命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看Harbor状态
kubectl get all -n harbor

# 查看日志
kubectl logs -f deployment/harbor-core -n harbor
kubectl logs -f deployment/harbor-registry -n harbor
kubectl logs -f deployment/harbor-portal -n harbor

# 重启组件
kubectl rollout restart deployment -n harbor

# 备份Harbor数据
kubectl exec -n harbor $(kubectl get pod -n harbor -l component=registry -o jsonpath='{.items[0].metadata.name}') -- tar czf /tmp/registry-backup.tar.gz /storage
kubectl cp harbor/$(kubectl get pod -n harbor -l component=registry -o jsonpath='{.items[0].metadata.name}'):/tmp/registry-backup.tar.gz ./registry-backup-$(date +%Y%m%d).tar.gz

# 查看资源使用情况
kubectl top pods -n harbor

十一、常见问题修复

问题现象 排查方向 修复方案
Harbor无法启动 数据库连接失败 检查PostgreSQL连接,验证用户名密码
证书错误 证书配置/Secret 检查cert-manager状态,验证证书Secret
镜像推送失败 认证/网络 检查Docker登录状态,验证网络连通性
PVC无法绑定 存储类/磁盘空间 检查local-path存储类,确认节点有足够空间
Trivy扫描失败 网络/资源 检查Trivy网络连通性,增加资源限制
Notary服务异常 证书/配置 检查Notary证书配置,验证域名解析

十二、配置参考

所有Harbor配置文件和部署脚本请参考:
https://gitee.com/Chemmy/kube-template/tree/master/devops/Harbor

该目录包含:

  • Harbor Helm values配置
  • 证书和路由配置
  • 数据库初始化脚本
  • 生产环境优化配置
  • 集成配置示例

总结

本文完成了Harbor在K3s集群中的标准化部署,实现了镜像仓库的完整功能。Harbor作为GitOps环境的镜像存储中枢,为自动化构建和部署提供了可靠的镜像管理能力。

部署完成后,建议配置Tekton Pipeline实现自动镜像构建和推送,配置ArgoCD实现镜像更新自动同步。至此,GitOps环境的所有核心组件已部署完成,形成了完整的自动化流水线。

一、核心定位

本文作为GitOps环境搭建系列的第八篇,聚焦Tekton CI/CD流水线的部署与配置。Tekton是云原生CI/CD框架,专为Kubernetes环境设计,提供声明式的流水线定义和任务执行能力。

在GitOps环境中,Tekton扮演”持续集成引擎”角色,作为GitOps流程的构建和测试环节,实现从代码提交到镜像构建的自动化。Tekton与Gitea、Harbor、ArgoCD协同工作,形成完整的”代码→构建→镜像→部署”自动化流水线。

二、部署前置检查

部署前需验证K3s集群状态及前序组件运行情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 验证K3s集群状态
kubectl get nodes

# 2. 验证ArgoCD运行状态
kubectl get pods -n argocd

# 3. 验证cert-manager运行状态
kubectl get pods -n cert-manager

# 4. 验证Traefik运行状态
kubectl get pods -n kube-system -l app=traefik

# 5. 验证Gitea运行状态
kubectl get pods -n gitea

# 6. 验证域名解析
nslookup tekton.example.io

前置条件检查清单:

  • K3s集群运行正常
  • ArgoCD GitOps核心可用
  • cert-manager证书管理可用
  • Traefik反向代理可用
  • Gitea代码仓库可用
  • 域名tekton.example.io已解析至K3s节点IP
  • Git仓库https://gitea.example.io/gitea_admin/devops-deploy.git已创建并包含Tekton配置

三、基于ArgoCD部署Tekton

3.1 准备Git仓库配置

在Gitea仓库devops-deploy.git中创建Tekton配置目录:

1
2
3
4
5
6
# 克隆仓库
git clone https://gitea.example.io/gitea_admin/devops-deploy.git
cd devops-deploy

# 创建Tekton配置目录
mkdir -p tekton

3.2 创建ArgoCD应用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# tekton/argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: tekton
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.example.io/gitea_admin/devops-deploy.git
path: tekton
targetRevision: HEAD
destination:
server: https://kubernetes.default.svc
namespace: tekton-pipelines
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m

3.3 创建Tekton核心部署配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# tekton/tekton-core.yaml
apiVersion: v1
kind: Namespace
metadata:
name: tekton-pipelines
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tekton-dashboard
namespace: tekton-pipelines
spec:
replicas: 1
selector:
matchLabels:
app: tekton-dashboard
template:
metadata:
labels:
app: tekton-dashboard
spec:
containers:
- name: dashboard
image: gcr.io/tekton-releases/github.com/tektoncd/dashboard/cmd/dashboard:v0.45.0
ports:
- containerPort: 9097
env:
- name: PORT
value: "9097"
- name: CLUSTER_NAME
value: "k3s-gitops"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: tekton-dashboard
namespace: tekton-pipelines
spec:
selector:
app: tekton-dashboard
ports:
- port: 9097
targetPort: 9097
type: ClusterIP

3.4 提交配置到Git仓库

1
2
3
4
# 添加配置文件
git add tekton/
git commit -m "feat: add Tekton deployment configuration"
git push origin main

四、配置HTTPS访问

4.1 创建Tekton证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# tekton/tekton-certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: tekton-cert
namespace: tekton-pipelines
spec:
secretName: tekton-tls-secret
issuerRef:
name: selfsigned-cluster-issuer
kind: ClusterIssuer
commonName: tekton.example.io
dnsNames:
- tekton.example.io
duration: 2160h
renewBefore: 360h
privateKey:
algorithm: RSA
size: 2048
usages:
- server auth

4.2 配置IngressRoute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# tekton/tekton-ingressroute.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: tekton-websecure
namespace: tekton-pipelines
spec:
entryPoints:
- websecure
routes:
- match: Host(`tekton.example.io`) && PathPrefix(`/`)
kind: Rule
services:
- name: tekton-dashboard
passHostHeader: true
port: 9097
tls:
secretName: tekton-tls-secret

4.3 应用ArgoCD配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 应用ArgoCD Application配置
kubectl apply -f tekton/argocd-application.yaml

# 或直接创建Application
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: tekton
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.example.io/gitea_admin/devops-deploy.git
path: tekton
targetRevision: HEAD
destination:
server: https://kubernetes.default.svc
namespace: tekton-pipelines
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
EOF

五、验证部署结果

5.1 验证ArgoCD同步状态

1
2
3
4
5
6
7
8
9
# 查看ArgoCD应用状态
argocd app get tekton
argocd app sync tekton # 手动触发同步

# 查看同步历史
argocd app history tekton

# 查看应用资源
argocd app resources tekton

5.2 验证Tekton组件状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看Tekton命名空间资源
kubectl get all -n tekton-pipelines

# 查看Dashboard Pod状态
kubectl get pods -n tekton-pipelines -l app=tekton-dashboard

# 查看Service状态
kubectl get svc -n tekton-pipelines tekton-dashboard

# 查看证书状态
kubectl get certificate -n tekton-pipelines
kubectl describe certificate tekton-cert -n tekton-pipelines

# 查看IngressRoute状态
kubectl get ingressroute -n tekton-pipelines

5.3 测试访问功能

1
2
3
4
5
6
7
8
# 测试HTTPS访问
curl -k https://tekton.example.io

# 查看Dashboard日志
kubectl logs -n tekton-pipelines -l app=tekton-dashboard --tail=50

# 进入容器检查状态
kubectl exec -it $(kubectl get pod -n tekton-pipelines -l app=tekton-dashboard -o jsonpath='{.items[0].metadata.name}') -n tekton-pipelines -- /bin/sh

六、配置示例流水线

6.1 创建Tekton Pipeline资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# tekton/example-pipeline.yaml
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: build-and-deploy
namespace: tekton-pipelines
spec:
params:
- name: git-url
type: string
description: Git repository URL
- name: image-name
type: string
description: Docker image name
tasks:
- name: fetch-source
taskRef:
name: git-clone
params:
- name: url
value: $(params.git-url)
- name: revision
value: main
workspaces:
- name: output
workspace: source

- name: build-image
runAfter: [fetch-source]
taskRef:
name: kaniko
params:
- name: IMAGE
value: $(params.image-name)
workspaces:
- name: source
workspace: source
- name: dockerconfig
workspace: docker-config

6.2 创建Tekton Task资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# tekton/tasks.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: git-clone
namespace: tekton-pipelines
spec:
workspaces:
- name: output
description: Git repository will be cloned here
params:
- name: url
description: Git repository URL
type: string
- name: revision
description: Git revision to clone
type: string
default: main
steps:
- name: clone
image: alpine/git
script: |
git clone $(params.url) $(workspaces.output.path)
cd $(workspaces.output.path)
git checkout $(params.revision)
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: kaniko
namespace: tekton-pipelines
spec:
workspaces:
- name: source
description: Source code workspace
- name: dockerconfig
description: Docker config.json workspace
params:
- name: IMAGE
description: Docker image name
type: string
steps:
- name: build-and-push
image: gcr.io/kaniko-project/executor:v1.9.0
env:
- name: DOCKER_CONFIG
value: /workspace/dockerconfig
command:
- /kaniko/executor
args:
- --dockerfile=$(workspaces.source.path)/Dockerfile
- --destination=$(params.IMAGE)
- --context=$(workspaces.source.path)

七、服务访问方式

7.1 集群内访问

  • Dashboard Web界面tekton-dashboard.tekton-pipelines.svc.cluster.local:9097
  • API访问:通过Service直接访问Tekton组件

7.2 集群外访问

  • Dashboard Web界面https://tekton.example.io
  • Git Webhook配置https://tekton.example.io(用于接收Gitea webhook)

八、日常运维命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看Tekton资源
kubectl get pipelines.tekton.dev -n tekton-pipelines
kubectl get tasks.tekton.dev -n tekton-pipelines
kubectl get pipelineruns.tekton.dev -n tekton-pipelines
kubectl get taskruns.tekton.dev -n tekton-pipelines

# 查看Dashboard日志
kubectl logs -f deployment/tekton-dashboard -n tekton-pipelines

# 重启Dashboard
kubectl rollout restart deployment tekton-dashboard -n tekton-pipelines

# 查看资源使用情况
kubectl top pods -n tekton-pipelines

# 清理完成的PipelineRun
kubectl delete pipelineruns.tekton.dev --all -n tekton-pipelines --field-selector=status.conditions[0].status=True

九、常见问题修复

问题现象 排查方向 修复方案
ArgoCD同步失败 Git仓库访问权限 检查ArgoCD Repository配置,添加访问凭证
Dashboard无法访问 IngressRoute/证书 检查IngressRoute配置,验证证书Secret是否存在
Pipeline执行失败 任务配置/资源权限 检查Task定义,验证ServiceAccount权限
镜像构建失败 Docker配置/网络 检查kaniko配置,验证网络连通性
Webhook不触发 Webhook配置/网络 检查Gitea webhook配置,验证网络可达性
资源占用过高 资源限制/并发数 调整资源限制,限制并发PipelineRun数量

十、配置参考

所有Tekton配置文件和部署脚本请参考:
https://gitee.com/Chemmy/kube-template/tree/master/devops/Tekton

该目录包含:

  • Tekton核心部署配置
  • 示例Pipeline和Task定义
  • Webhook集成配置
  • 生产环境优化配置
  • 监控和日志配置

总结

本文完成了Tekton在K3s集群中的标准化部署,基于ArgoCD实现了GitOps方式的CI/CD流水线管理。Tekton作为GitOps环境的持续集成引擎,为自动化构建和测试提供了强大的能力。

部署完成后,建议创建示例流水线验证构建功能,配置Gitea webhook实现自动触发,并设置适当的资源限制。下一篇文章将部署Harbor镜像仓库,为GitOps环境提供镜像存储和分发能力。

一、核心定位

本文作为GitOps环境搭建系列的第七篇,聚焦核心组件ArgoCD的部署与配置。ArgoCD是Argo Project开源的声明式GitOps持续部署工具,专为Kubernetes应用部署与生命周期管理设计。

在GitOps环境中,ArgoCD扮演”部署自动化中枢”角色,作为GitOps流程的核心执行引擎,实现”Git为单一可信数据源”的理念。ArgoCD自动对比Git仓库中的期望状态与K3s集群中的实际状态,完成配置同步与部署,实现从代码到部署的完整自动化。

二、部署前置检查

部署前需验证K3s集群状态及前序组件运行情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 验证K3s集群状态
kubectl get nodes

# 2. 验证Traefik运行状态
kubectl get pods -n kube-system -l app=traefik

# 3. 验证cert-manager运行状态
kubectl get pods -n cert-manager

# 4. 验证Gitea运行状态
kubectl get pods -n gitea

# 5. 验证Helm可用性
helm version --short

# 6. 验证域名解析
nslookup argocd.example.io

前置条件检查清单:

  • K3s集群运行正常
  • Traefik反向代理可用
  • cert-manager证书管理可用
  • Gitea代码仓库可用(可选,用于测试)
  • 域名argocd.example.io已解析至K3s节点IP
  • Helm工具可用

三、标准化部署ArgoCD

3.1 环境准备

3.1.1 添加ArgoCD Helm仓库

1
2
3
# 添加ArgoCD官方Helm仓库
helm repo add argo https://argoproj.github.io/argo-helm --force-update
helm repo update

3.1.2 创建命名空间

1
2
# 创建ArgoCD专属命名空间
kubectl create namespace argocd

3.2 编写配置文件

创建argocd-values.yaml配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# argocd-values.yaml
# 参考:https://gitee.com/Chemmy/kube-template/blob/master/devops/ArgoCD/argocd-values.yaml

## 全局配置
global:
# 设置访问域名为 argocd.example.io
domain: argocd.example.io

## Argo CD 核心配置
configs:
cm:
# 启用本地 admin 用户
admin.enabled: true

params:
# 禁用 Argo CD Server 的 HTTPS,让 Traefik 处理 TLS
server.insecure: "true"

## Server 配置
server:
# 启用 Ingress,由 Traefik 接管
ingress:
enabled: true
# 指定 IngressClass 为 traefik(K3s 默认)
annotations:
traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.entrypoints: websecure
ingressClassName: "traefik"
hosts:
- argocd.example.io

service:
# 使用 ClusterIP,Traefik 通过 Service 发现后端
type: ClusterIP

## Redis(Argo CD 必需,保持启用)
redis:
enabled: true

## 关闭通知模块节省资源
notifications:
enabled: false

## Dex(如无需 SSO 登录,可关闭以节省资源)
dex:
enabled: false

## 其他组件保持默认
crds:
install: true
keep: true

3.3 一键部署ArgoCD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 部署ArgoCD
helm install argocd argo/argo-cd \
--namespace argocd \
--version 9.4.1 \
-f argocd-values.yaml

# 或使用在线配置
helm install argocd argo/argo-cd \
--namespace argocd \
--version 9.4.1 \
--set global.domain=argocd.example.io \
--set server.ingress.enabled=true \
--set server.ingress.ingressClassName=traefik \
--set server.ingress.hosts[0]=argocd.example.io \
--set server.insecure=true \
--set notifications.enabled=false \
--set dex.enabled=false

3.4 验证部署状态

1
2
3
4
5
6
7
8
9
10
11
# 查看ArgoCD Pod状态
kubectl get pods -n argocd -w

# 查看Ingress资源
kubectl get ingress -n argocd

# 查看ArgoCD服务状态
kubectl get svc -n argocd

# 查看部署完成状态
kubectl wait --for=condition=Ready pods -l app.kubernetes.io/name=argocd-server -n argocd --timeout=300s

四、配置HTTPS访问

4.1 创建ArgoCD证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# argocd-certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: argocd-cert
namespace: argocd
spec:
secretName: argocd-tls-secret
issuerRef:
name: selfsigned-cluster-issuer
kind: ClusterIssuer
commonName: argocd.example.io
dnsNames:
- argocd.example.io
duration: 2160h
renewBefore: 360h
privateKey:
algorithm: RSA
size: 2048
usages:
- server auth

4.2 配置IngressRoute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# argocd-ingressroute.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: argocd-websecure
namespace: argocd
spec:
entryPoints:
- websecure
routes:
- match: Host(`argocd.example.io`) && PathPrefix(`/`)
kind: Rule
services:
- name: argocd-server
passHostHeader: true
port: 80
tls:
secretName: argocd-tls-secret

4.3 应用HTTPS配置

1
2
3
4
5
6
7
# 应用证书和路由配置
kubectl apply -f argocd-certificate.yaml
kubectl apply -f argocd-ingressroute.yaml

# 验证证书状态
kubectl get certificate -n argocd
kubectl describe certificate argocd-cert -n argocd

五、访问与初始化ArgoCD

5.1 获取初始密码

1
2
3
4
5
6
# 获取初始管理员密码
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo

# 或使用更安全的方式
ARGOCD_PASSWORD=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
echo "ArgoCD初始密码: $ARGOCD_PASSWORD"

5.2 访问ArgoCD Web UI

  1. 浏览器访问:https://argocd.example.io
  2. 用户名:admin
  3. 密码:使用上述获取的初始密码
  4. 首次登录后立即修改密码(User Info → Update Password)

5.3 CLI工具安装与配置(可选)

1
2
3
4
5
6
7
8
9
10
11
12
13
# Linux系统安装ArgoCD CLI
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 755 argocd-linux-amd64 /usr/local/bin/argocd
rm -f argocd-linux-amd64

# 验证安装
argocd version --client

# 登录ArgoCD
argocd login argocd.example.io --grpc-web --username admin --password $ARGOCD_PASSWORD

# 或使用token登录(更安全)
argocd account generate-token --account admin

六、验证部署结果

6.1 验证组件状态

1
2
3
4
5
6
7
8
9
10
11
# 查看所有ArgoCD组件状态
kubectl get all -n argocd

# 查看证书和路由状态
kubectl get certificate,ingressroute -n argocd

# 查看ArgoCD日志
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-server --tail=50

# 测试HTTPS访问
curl -k https://argocd.example.io

6.2 测试GitOps功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 创建测试应用(使用Gitea仓库)
cat > test-application.yaml << EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: test-app
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.example.io/gitea_admin/test-repo.git
targetRevision: HEAD
path: k8s/
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
EOF

# 应用测试配置
kubectl apply -f test-application.yaml

# 查看应用状态
kubectl get application -n argocd
argocd app get test-app

6.3 清理测试资源

1
2
# 清理测试应用
kubectl delete -f test-application.yaml

七、生产环境配置建议

7.1 安全加固

1
2
3
4
5
6
7
8
9
10
# 在argocd-values.yaml中添加安全配置
server:
extraArgs:
- --insecure
- --disable-auth
- --rootpath=/argocd
ingress:
annotations:
traefik.ingress.kubernetes.io/whitelist-source-range: "192.168.0.0/16,10.0.0.0/8"
traefik.ingress.kubernetes.io/rate-limit: "10"

7.2 资源优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 调整资源限制
controller:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"

server:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"

repoServer:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"

7.3 监控配置

1
2
3
4
5
6
7
8
9
10
# 启用监控
controller:
metrics:
enabled: true
serviceMonitor:
enabled: true

server:
metrics:
enabled: true

八、日常运维命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 查看ArgoCD状态
argocd version
argocd cluster list

# 管理应用
argocd app list
argocd app get <app-name>
argocd app sync <app-name>
argocd app history <app-name>
argocd app rollback <app-name> --id <revision-id>

# 管理项目
argocd proj list
argocd proj get <project-name>

# 查看日志
kubectl logs -f deployment/argocd-server -n argocd
kubectl logs -f deployment/argocd-repo-server -n argocd
kubectl logs -f deployment/argocd-application-controller -n argocd

# 重启组件
kubectl rollout restart deployment argocd-server -n argocd
kubectl rollout restart deployment argocd-repo-server -n argocd
kubectl rollout restart deployment argocd-application-controller -n argocd

九、常见问题修复

问题现象 排查方向 修复方案
无法访问Web UI 域名解析/IngressRoute 检查域名解析,验证IngressRoute配置,查看Traefik日志
证书警告或错误 证书签发/Secret 检查cert-manager状态,验证证书Secret是否存在
无法同步Git仓库 网络/认证 检查网络连通性,配置SSH密钥或访问令牌
资源同步失败 权限/资源配置 检查ServiceAccount权限,验证资源配置格式
CLI登录失败 网络/认证 检查网络代理,验证用户名密码,使用–grpc-web参数
内存占用过高 资源限制/应用数量 调整资源限制,减少监控的应用数量

十、配置参考

所有ArgoCD配置文件和部署脚本请参考:
https://gitee.com/Chemmy/kube-template/tree/master/devops/ArgoCD

该目录包含:

  • ArgoCD Helm values配置
  • 证书和路由配置
  • 生产环境优化配置
  • 示例应用配置
  • 监控和备份脚本

总结

本文完成了ArgoCD在K3s集群中的标准化部署,实现了GitOps核心组件的完整功能。ArgoCD作为GitOps环境的核心执行引擎,为自动化部署提供了强大的能力。

部署完成后,建议创建测试应用验证同步功能,配置自动同步策略,并设置适当的资源限制。下一篇文章将部署Tekton CI流水线,为GitOps环境提供持续集成能力。

本文档基于一篇关于在 Kylin V10 上安装 NVIDIA 驱动、CUDA 和 Docker 的实践笔记整理,旨在为使用 NVIDIA GeForce RTX 3060 Ti 显卡的用户提供清晰的离线安装与配置步骤。

1. 系统环境确认

首先,确认您的操作系统版本。

1
cat /etc/kylin-release

预期输出应类似 Kylin Linux Advanced Server release V10 (Sword)

2. 安装 NVIDIA 显卡驱动

步骤 1:禁用系统自带的开源驱动 (nouveau)

  1. 编辑黑名单配置文件:
    1
    sudo vi /usr/lib/modprobe.d/dist-blacklist.conf
  2. 找到 blacklist nvidiafb 一行,在行首添加 # 将其注释掉。
  3. 在文件末尾添加以下两行内容:
    1
    2
    blacklist nouveau
    options nouveau modeset=0
  4. 保存并退出编辑器。

步骤 2:卸载开源驱动模块并重启

1
2
sudo rmmod nouveau
sudo reboot

步骤 3:安装驱动编译依赖
重启后,安装编译 NVIDIA 驱动所需的内核开发包和工具。

1
sudo dnf install gcc kernel-devel-$(uname -r) kernel-headers-$(uname -r) make elfutils-libelf-devel tar bzip2 dkms acpid wget -y

步骤 4:下载并安装官方驱动

  1. 下载驱动:访问 NVIDIA 官方驱动下载页面。选择产品类型为 GeForce RTX 3060 Ti,操作系统选择 Linux 64-bit,点击“搜索”并下载推荐的驱动版本(例如 550 或 575 系列)。将下载的 .run 文件(如 NVIDIA-Linux-x86_64-550.90.07.run)上传到服务器。
  2. 安装驱动
    1
    2
    3
    4
    # 赋予安装脚本执行权限
    chmod +x NVIDIA-Linux-x86_64-*.run
    # 运行安装程序,按照提示操作(通常选择默认选项即可)
    sudo ./NVIDIA-Linux-x86_64-*.run
  3. 验证安装:安装完成后,运行以下命令检查显卡是否被正确识别。
    1
    nvidia-smi
    如果看到显卡型号、驱动版本和 GPU 状态信息,则表示驱动安装成功。

3. 安装 CUDA 工具包

CUDA 是进行 GPU 计算的基础平台。

  1. 下载 CUDA:访问 NVIDIA CUDA 下载页面。选择操作系统为 Linux -> x86_64 -> Kylin -> 10 -> rpm (local)。页面会生成安装命令。
  2. 执行安装命令:复制页面提供的命令在终端中执行,例如:
    1
    2
    3
    4
    wget https://developer.download.nvidia.com/compute/cuda/repos/kylin10/x86_64/cuda-keyring_1.1-1_all.deb
    sudo dpkg -i cuda-keyring_1.1-1_all.deb
    sudo apt-get update
    sudo apt-get -y install cuda
    注意:实际命令请以官网生成的最新命令为准。
  3. 配置环境变量
    1
    2
    3
    echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc
    echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
    source ~/.bashrc
  4. 验证安装
    1
    nvcc -V
    此命令应输出已安装的 CUDA 版本号。

4. 离线安装 Docker 并配置 GPU 支持

步骤 1:离线安装 Docker
请参考笔记 麒麟V10离线安装Docker 中详细的分架构(x86_64)离线安装步骤。该指南提供了从外网准备 RPM 包到内网服务器安装、启动和验证的完整流程。请确保按照该指南完成 Docker 的离线安装,并成功运行 docker --versiondocker ps 命令进行验证。

步骤 2:安装 NVIDIA Container Toolkit (为 Docker 添加 GPU 支持)
这是让 Docker 容器能够使用宿主 GPU 的关键步骤。

  1. 配置仓库并安装:
    1
    2
    3
    4
    distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
    curl -s -L https://nvidia.github.io/libnvidia-container/gpgkey | sudo apt-key add -
    curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
    sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
  2. 配置 Docker 使用 NVIDIA 作为默认运行时:
    1
    2
    sudo nvidia-ctk runtime configure --runtime=docker
    sudo systemctl restart docker
  3. 验证 Docker GPU 支持
    运行一个测试容器,检查容器内是否能识别到 GPU。
    1
    sudo docker run --rm --gpus all nvidia/cuda:12.1.1-base-ubuntu22.04 nvidia-smi
    如果此命令在容器内成功输出了与宿主机 nvidia-smi 类似的 GPU 信息,则表明 Docker 已成功配置 GPU 支持,您的 RTX 3060 Ti 现在可以在容器中用于加速计算任务(如 AI 训练、图形渲染等)。

总结

整个过程可以概括为:禁用开源驱动 -> 安装 NVIDIA 官方驱动 -> 安装 CUDA 计算平台 -> 参考专用指南离线安装 Docker 引擎 -> 为 Docker 安装 NVIDIA 容器工具包。完成这些步骤后,您的麒麟 V10 系统就具备了完整的 NVIDIA GPU 计算与容器化支持环境。

在麒麟V10桌面操作系统的日常运维与开发工作中,SSH(Secure Shell)作为远程开发、虚拟机管理及内网服务器连接的核心工具,其运行稳定性直接影响运维效率与开发进度。当出现“Connection refused”“Connection timed out”等连接异常提示时,运维人员与开发人员常面临排查无头绪的困境——此类故障可能源于服务运行状态、配置参数设置、网络通路连通性、防火墙规则配置或用户权限管控等多个环节,需通过分层拆解实现根源定位。

在团队内部麒麟V10开发桌面部署过程中,曾出现一起由旧版配置残留导致的SSH连接异常,排查耗时较长。该案例表明,掌握系统性的故障诊断方法论,相较于记忆零散操作命令更具实践价值。本文基于实战经验,梳理形成从现象到根源、从通用场景到特殊场景的分层排查体系,覆盖五大常见故障类型,深入解析故障产生的技术原理,补充安全实用的配置方案,提供可直接复用的操作命令与排查技巧,为相关人员解决SSH连接故障提供技术支撑。

一、基础排查:SSH守护进程(sshd)运行状态校验

SSH连接建立的前提是目标主机的sshd服务正常运行,该环节作为基础排查要点,易被忽视。在系统新安装、重启或配置变更后,需优先对sshd服务状态进行校验。

1.1 服务状态查询与异常分析

麒麟V10系统中,OpenSSH服务通常由systemd进行管理,最常用的服务状态查询命令为sudo systemctl status ssh。查看命令输出时,需重点关注核心信息,不可仅依据“active (running)”的表面状态判断服务正常。

1
sudo systemctl status ssh

健康的sshd服务输出需包含以下三项关键信息,缺一不可:

  • Loaded行:显示为enabled,表示服务已配置开机自启;若显示为disabled,则系统重启后服务无法自动运行,需手动启动。

  • Active行:active (running)为服务正常运行的理想状态;若显示为active (exited),表明服务启动后异常退出,需进一步排查启动失败原因。

  • 日志片段:若输出中出现Failed to listen on port 22Address already in use,则可直接判定为端口冲突问题,需优先处理端口占用异常。

若sshd服务未处于运行状态,不可盲目启动服务,应先通过日志排查启动失败根源,避免操作不当加剧故障:

1
sudo journalctl -u ssh --since "5 minutes ago" --no-pager

说明:journalctl为systemd系统的日志查看工具,-u ssh参数用于指定查看sshd服务相关日志,--since参数用于过滤最近5分钟的日志内容。通过查看启动失败的详细错误信息,可快速定位服务启动类故障的核心原因,为后续修复提供依据。

1.2 服务安装、启动与自启配置

若经排查确认sshd服务未安装(如最小化安装的麒麟V10系统),需先安装openssh-server组件,确保服务运行所需组件完整:

1
2
sudo apt update
sudo apt install openssh-server -y

组件安装完成后,需启动sshd服务并配置开机自启,最后再次校验服务状态,确保服务正常运行:

1
2
3
sudo systemctl start ssh      # 立即启动sshd服务
sudo systemctl enable ssh # 配置服务开机自启
sudo systemctl status ssh # 校验服务运行状态

进阶排查要点:部分场景下,服务状态虽显示为“active (running)”,但实际未监听目标端口,需通过ss或netstat命令进行交叉验证,排除服务“假运行”情况:

1
2
3
sudo ss -tlnp | grep :22
# 或使用netstat(部分系统默认未预装,需提前安装)
sudo netstat -tlnp | grep :22

正常情况下,命令输出应显示sshd进程监听0.0.0.0:22(IPv4所有网络接口)和[::]:22(IPv6所有网络接口);若无相关输出,说明sshd进程可能绑定至特定IP地址,或未成功监听目标端口,需进一步排查配置文件。

二、核心配置:sshd_config参数解析及推荐配置

若sshd服务运行正常且端口监听正常,但SSH连接仍存在异常,故障大概率源于核心配置文件/etc/ssh/sshd_config。该文件用于控制sshd守护进程的运行规则,参数配置错误是导致SSH连接失败的高频原因,同时合理的配置可有效提升SSH连接的安全性。

2.1 必查核心配置项(故障排查重点)

修改配置文件前,需先进行备份操作,避免配置错误导致sshd服务无法启动,备份命令如下:sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak。备份完成后,可通过vi或nano编辑器对配置文件进行修改。以下为导致SSH连接失败的高频配置项,需重点排查:

配置项 默认值/常见值 作用与影响 排查建议值
Port 22 指定SSH服务监听的端口号,默认22端口易遭受扫描攻击,可根据实际需求自定义修改 确认端口配置与预期一致(如默认22端口),若已修改端口,客户端连接时需通过-p参数指定端口
ListenAddress 0.0.0.0 指定sshd进程监听的IP地址,0.0.0.0表示监听所有IPv4网络接口 若配置为127.0.0.1或特定IP地址,仅该IP地址可建立SSH连接,排查时需确认是否存在误配置
PermitRootLogin prohibit-password 控制是否允许root用户直接通过SSH登录,直接允许root用户登录存在安全风险 故障调试阶段可临时设置为yes,生产环境建议配置为no或prohibit-password,提升系统安全性
PasswordAuthentication yes 控制是否允许通过密码认证方式登录SSH,密码认证易遭受暴力破解攻击 密码连接失败时,需确认该参数配置为yes;生产环境建议关闭密码认证,仅启用公钥认证
PubkeyAuthentication yes 控制是否允许通过公钥认证方式登录SSH,公钥认证为更安全的登录方式 公钥连接失败时,需确认该参数配置为yes,且公钥相关配置正确无误
AllowUsers 未设置 配置允许通过SSH登录的用户白名单,设置后仅白名单内用户可建立连接 若已配置该参数,需确认登录用户名已纳入白名单,否则无法建立SSH连接
DenyUsers 未设置 配置禁止通过SSH登录的用户黑名单,设置后黑名单内用户无法建立连接 排查时需确认登录用户名未被纳入黑名单,避免因误配置导致连接失败

2.2 推荐配置(安全与稳定兼顾,可直接复用)

结合麒麟V10桌面版系统特性,梳理以下sshd_config推荐配置,兼顾安全性与实用性,可直接添加至配置文件末尾(需根据实际应用场景调整),配置完成后需重启sshd服务使配置生效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 自定义SSH端口(避免默认22端口遭受扫描,建议选择10000-65535区间的端口)
Port 2222

# 禁止root用户直接登录,提升系统安全性
PermitRootLogin no

# 关闭密码认证,仅启用公钥认证(需提前完成公钥配置)
PasswordAuthentication no

# 启用公钥认证(默认启用,明确配置可提升配置可靠性)
PubkeyAuthentication yes

# 指定公钥文件路径(默认路径,无需修改,需确保文件权限正确)
AuthorizedKeysFile .ssh/authorized_keys

# 限制连接超时时间,避免空闲连接占用系统资源
ClientAliveInterval 60
ClientAliveCountMax 3

# 禁止空密码登录(默认禁止,明确配置可规范配置标准)
PermitEmptyPasswords no

# 限制并发连接数,可根据服务器性能灵活调整
MaxSessions 10
MaxStartups 10:30:60

注意:修改Port端口配置后,客户端建立SSH连接时需使用ssh -p 2222 user@hostname命令(将2222替换为自定义端口);关闭密码认证前,需确认公钥认证已配置完成,避免出现无法登录的情况。

任何配置修改完成后,需重启sshd服务使配置生效;部分无需重启服务即可生效的参数,可通过重载配置实现更新:

1
2
3
sudo systemctl restart ssh
# 或重载配置(适用于AllowUsers、DenyUsers等部分参数)
sudo systemctl reload ssh

2.3 配置文件语法排错技巧

SSH配置文件对语法规范性要求严格,轻微语法错误即可导致sshd服务无法启动,以下为常见语法错误及排查技巧:

  • 多余空格:如PermitRootLogin yes(两个空格分隔)可能导致参数识别异常,PermitRootLogin = yes(添加等号)属于语法错误,正确格式为PermitRootLogin yes(单个空格分隔)。

  • 注释符干扰:以#开头的行为注释行,需确保待修改行未被注释(行首无#);若需启用注释行中的配置,删除行首#即可。

  • Include指令影响:配置文件可能通过Include指令引入其他配置片段,若核心配置排查无异常,需检查引入的配置文件是否存在冲突。

可通过sshd测试模式快速检查配置文件语法有效性,该模式仅解析配置文件并报告错误,不实际重启服务,可避免因配置错误导致服务中断:

1
sudo sshd -t

若命令无输出,表明配置文件语法正确;若存在错误提示,可根据提示定位并修改对应参数。

三、通路排查:网络连通性与防火墙规则解析

当sshd服务运行正常且配置无误时,SSH连接异常通常源于网络通路问题,主要包括网络不可达与防火墙拦截两类情况。需从网络连通性与防火墙规则两个核心维度进行排查,确保连接通路畅通。

3.1 基础网络连通性测试(快速定位底层故障)

在客户端主机上,可通过ping命令测试目标服务器IP地址的连通性(假设服务器IP为192.168.1.100),初步排除底层网络故障:

1
ping -c 4 192.168.1.100

排查说明:

  • 若ping命令执行失败,表明存在底层网络故障,如网线松动、IP地址配置错误、路由器拦截、网段隔离等,需先解决网络连通性问题,再排查SSH服务本身。

  • 若ping命令执行成功但SSH连接失败,表明网络可达,故障集中于SSH端口未开放或防火墙拦截,需进一步检查目标端口开放状态。

可通过telnet命令测试SSH端口(默认22端口,自定义端口需对应替换)的开放情况:

1
telnet 192.168.1.100 22

若连接成功,将显示类似SSH-2.0-OpenSSH_8.9p1的横幅信息;若连接被拒绝或超时,表明目标端口未开放或被防火墙拦截。

说明:麒麟V10桌面版默认可能未安装telnet客户端,可使用nc(netcat)工具替代,该工具命令更简洁、输出更清晰,具体命令为:nc -zv 192.168.1.100 22,其中-z参数用于端口扫描,-v参数用于输出详细信息,可快速判断端口开放状态。

3.2 防火墙规则深度排查(重点解决拦截问题)

麒麟V10桌面版通常采用firewalld或ufw作为防火墙前端工具,底层依赖iptables/nftables实现规则管控。需逐层检查防火墙规则,避免SSH端口被拦截,具体排查方法如下:

1. 检查ufw防火墙(若启用)

ufw为麒麟V10桌面版常用的简易防火墙管理工具,可通过以下命令查看当前防火墙状态及规则:

1
sudo ufw status verbose

排查重点:确认SSH端口(默认22端口,自定义端口需对应)的规则为ALLOW IN;若规则为DENY,需执行以下命令开放端口:

1
2
sudo ufw allow 22/tcp  # 开放22端口(SSH默认使用TCP协议)
sudo ufw reload # 重载防火墙规则,使配置生效

2. 检查firewalld防火墙(若启用)

部分麒麟V10系统可能启用firewalld防火墙,可通过以下命令查看当前防火墙规则:

1
sudo firewall-cmd --list-all

排查重点:确认services:列表中包含ssh服务,或ports:列表中包含22/tcp端口;若未包含,需添加对应规则并重载:

1
2
3
4
5
# 方法1:添加ssh服务(自动对应22端口)
sudo firewall-cmd --permanent --add-service=ssh
# 方法2:添加指定端口(自定义端口时使用,如2222)
# sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload # 重载防火墙规则

3. 直接检查iptables规则(终极排查手段)

ufw与firewalld的配置最终均会同步至iptables/nftables,直接查看iptables规则可避免前端工具配置的误导,快速定位拦截故障:

1
sudo iptables -L -n --line-numbers

排查重点:查看INPUT链(入站规则),确认存在针对tcp dpt:22(22替换为自定义端口)的允许规则,典型允许规则如下:

1
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:22 ctstate NEW,ESTABLISHED

注意:若INPUT链前端存在DROPREJECT所有流量的规则,且允许SSH连接的规则未优先生效,将导致SSH连接被拦截,需调整规则顺序或修改拦截规则。

四、认证排查:密码与公钥认证故障精准修复

若能正常连接sshd服务,但在登录阶段出现失败,属于认证类故障。SSH协议主要支持密码认证与公钥认证两种方式,需结合登录提示信息,针对性排查,解决认证不匹配问题。

4.1 密码认证失败排查(简易流程)

确认服务端PasswordAuthentication参数已配置为yes,但密码登录仍失败时,可按以下顺序排查,高效定位故障原因:

  • 用户名有效性:SSH连接所使用的用户名为麒麟V10系统的操作系统用户,安装系统时创建的普通用户与root用户为不同账户。使用ssh user@hostname命令连接时,需确认user为系统已存在的用户(可通过cat /etc/passwd命令查看系统用户列表)。

  • 密码正确性:SSH登录密码区分大小写,需检查键盘布局是否正常(如CapsLock键是否误触发)。可先在服务端本地终端使用该用户登录,验证密码有效性,排除密码输入错误。

  • 用户账户状态:检查用户账户是否处于锁定或过期状态,执行以下命令查看账户状态: sudo passwd -S <用户名> # 替换<用户名>为实际登录用户 若状态显示为L(锁定)或NP(无密码),需执行解锁命令(sudo passwd -u <用户名>)或重置密码(sudo passwd <用户名>)。

  • PAM模块限制:/etc/pam.d/目录下的配置文件(如sshd)可能存在额外登录限制,如终端限制、登录时间限制等。若未手动修改过相关配置,此类限制通常不是故障根源,可暂不排查。

4.2 公钥认证失败排查(实战重点,高安全性)

公钥认证失败的排查过程相对复杂,涉及客户端与服务端的多文件匹配及权限配置,需分别对两端进行检查,具体排查清单如下:

服务端检查清单(核心重点)

  1. 公钥文件权限检查(最常见故障点):sshd服务对文件权限要求严格,~/.ssh/authorized_keys文件及其父目录权限过松,会导致sshd服务出于安全考虑拒绝使用该文件,需配置正确权限: chmod 700 ~/.ssh # 仅当前用户拥有读写执行权限 chmod 600 ~/.ssh/authorized_keys # 仅当前用户拥有读写权限 chown -R $USER:$USER ~/.ssh # 确保文件归属当前用户

  2. 公钥内容检查:确保authorized_keys文件中粘贴的公钥为完整单行内容,无换行、多余空格或特殊字符。建议使用ssh-copy-id命令重新推送公钥,该命令可自动处理公钥格式与文件权限:# 在客户端执行,替换用户与服务器IP地址 ssh-copy-id user@192.168.1.100

  3. sshd配置确认:再次检查sshd_config文件,确保PubkeyAuthentication参数配置为yes,且AuthorizedKeysFile参数指向正确(默认路径为.ssh/authorized_keys,无需修改)。

客户端检查清单

  1. 私钥权限检查:客户端私钥文件(默认路径为~/.ssh/id_rsa)需配置严格权限,否则会被客户端SSH工具拒绝使用,具体权限配置命令: chmod 600 ~/.ssh/id_rsa

  2. 指定密钥文件:若使用非默认私钥(如自定义密钥文件名或路径),建立连接时需通过-i参数指定私钥路径: ssh -i /path/to/your/private_key user@hostname

  3. 启用详细调试(终极排查手段):建立连接时添加-vvv参数,可输出详细的认证过程,清晰呈现每一步的执行情况及失败原因,重点关注Offering public keyAuthentication succeeded/failed等关键日志行:ssh -vvv user@hostname

五、进阶排查:隐性故障与特殊场景处理

若经上述四层排查后,SSH连接仍存在异常,故障可能源于系统安全模块、资源限制或客户端缓存等隐性因素,需进一步深入排查,覆盖各类特殊场景。

5.1 SELinux/AppArmor强制访问控制的影响

麒麟V10系统可能搭载强制访问控制模块,其中桌面版默认通常不强制启用SELinux,但AppArmor模块可能处于活跃状态。该模块的严格访问控制规则可能阻止sshd进程访问必要资源(如authorized_keys文件、私钥文件等),导致SSH连接失败。

通过以下命令检查AppArmor状态,确认sshd服务是否被管控:

1
sudo aa-status | grep sshd

排查与处理方案:

  • 若sshd服务处于enforce模式(强制管控),且系统日志(/var/log/auth.log/var/log/audit/audit.log)中存在“拒绝访问”相关记录,表明AppArmor模块拦截了sshd进程的操作。

  • 临时调试方案:将sshd服务的AppArmor模式调整为complain(仅输出警告,不执行拦截),测试SSH连接是否恢复正常,命令如下: sudo aa-complain /usr/sbin/sshd

重要提示:临时调试完成后,需根据实际安全需求制定合理的AppArmor策略,或恢复为enforce模式,避免降低系统安全防护等级。

5.2 系统资源与连接限制排查

系统资源不足或连接数限制也可能导致SSH连接失败,重点排查以下三项内容:

  • 最大连接数限制:sshd_config文件中的MaxSessions(最大并发会话数)与MaxStartups(未完成认证的最大连接数)参数,若当前连接数达到限制,新的连接尝试会被拒绝,可根据服务器性能适当调大参数值。

  • PAM资源限制:通过ulimit命令或PAM模块设置的用户进程数、文件描述符数等资源限制,可能影响sshd进程创建子进程处理连接。可通过ulimit -a命令查看当前资源限制,必要时进行调整。

  • TCP Wrappers限制:/etc/hosts.allow/etc/hosts.deny文件为古老的访问控制配置文件,可能存在针对sshd服务的拒绝规则。需检查该文件,若存在相关拒绝规则,需删除或修改。

5.3 客户端侧配置与缓存故障排查

排查过程中不可仅关注服务端,客户端的配置错误或缓存问题也可能导致SSH连接失败,重点排查以下三项内容:

  • 客户端配置文件干扰:客户端~/.ssh/config文件的配置优先级较高,若该文件中为目标主机配置了错误参数(如错误端口、用户名、密钥文件路径、代理设置等),会导致连接失败,需检查并修改该文件。

  • known_hosts文件缓存问题:若服务器重装系统或更换SSH密钥,客户端会因主机密钥不匹配拒绝建立连接,提示“Host key verification failed”。需删除客户端~/.ssh/known_hosts文件中对应主机的条目,或通过以下命令快速删除:ssh-keygen -R hostname # 替换hostname为服务器IP地址或主机名

  • 网络代理干扰:客户端shell环境中的http_proxyall_proxy等环境变量,若配置了网络代理,可能干扰SSH直接连接。需通过echo $http_proxy命令检查代理配置,必要时通过unset http_proxy all_proxy命令临时关闭代理后重试。

总结:SSH连接故障排查核心逻辑

麒麟V10桌面版SSH连接失败的排查工作,本质是一套分层诊断流程:从基础的sshd服务运行状态校验,到核心的sshd_config配置参数排查,再到网络连通性与防火墙规则解析,随后进行认证凭据匹配排查,最后处理系统级隐性故障。通过层层递进的方式,逐步缩小排查范围,可实现故障的高效定位与修复。

核心排查技巧:结合日志与调试输出进行分析——服务端/var/log/auth.log(或/var/log/secure)日志可提供sshd服务运行状态及认证失败原因,客户端ssh -vvv命令可输出详细的连接与认证过程,二者结合可快速定位故障根源。

养成“故障排查先查日志、先基础后复杂”的习惯,可大幅降低盲目操作的概率,提升排查效率。实际上,绝大多数SSH连接失败故障均可通过本文梳理的五个层次实现解决,掌握该套排查体系,可有效提升SSH连接故障的处理能力,保障远程运维与开发工作的顺利开展。

在国产化替代加速推进的当下,越来越多政企单位开始部署搭载麒麟V10操作系统的服务器,涵盖arm64、x86_64两种主流架构。这类服务器常应用于涉密项目、内部研发等无外网访问权限的场景,如何在完全隔离的内网环境中,快速、稳定地部署Docker及验证环境(本文以Redis为例),成为很多运维人员面临的难题。

常规外网环境中,docker pull redis 即可轻松获取镜像并部署,但不同架构的麒麟V10服务器无法直接拉取非对应架构的默认镜像,且内网环境无法在线下载依赖和镜像,导致部署过程坑点重重。本文将从Docker离线安装入手,兼顾arm64、x86_64两种架构,手把手带你完成麒麟V10环境下Docker离线部署、Redis镜像离线获取与加载、容器启动与环境验证的全流程,兼顾实用性与安全性,确保新手也能跟着操作落地,Redis仅作为Docker环境部署完成后的验证工具,无需复杂配置。

一、前置说明:环境与核心思路

1.1 环境确认

本次操作针对 麒麟V10操作系统(含arm64/x86_64两种架构,arm64即aarch64,x86_64即x86),目标服务器为无外网访问权限的内网服务器。操作前需准备两台机器,且中转站机器架构建议与目标服务器一致(避免镜像架构不兼容):

  • 目标服务器:内网麒麟V10(arm64或x86_64),用于最终部署Docker和Redis(环境验证);

  • 中转站机器:能访问外网的Linux机器(优先与目标服务器同架构,arm64可选麒麟V10、华为云鲲鹏实例,x86_64可选普通Linux服务器、虚拟机;若架构不同,需额外注意镜像架构指定)。

1.2 核心思路

离线部署的核心是“外网准备、内网部署、环境验证”,整体流程可概括为3步,兼顾两种架构适配:

  1. 外网中转站:根据目标服务器架构,下载对应版本的Docker离线安装包、Redis Docker镜像,并打包保存;

  2. 内网传输:将离线安装包、镜像包通过U盘、内部文件服务器等介质,传输到麒麟V10目标服务器;

  3. 内网部署:依次完成Docker离线安装、Redis镜像加载、容器启动,通过简单命令验证Docker与Redis环境正常运行。

二、第一步:外网准备——下载Docker离线包与Redis镜像(分架构)

此步骤在能访问外网的中转站机器上操作,核心是根据目标服务器架构(arm64/x86_64),获取适配麒麟V10的Docker安装包和Redis镜像,避免架构不兼容问题。Redis仅作为环境验证,选择官方稳定版(本文以Redis 6.2.6为例,适配两种架构)。

2.1 下载麒麟V10(分架构)Docker离线安装包

麒麟V10系统基于CentOS/RHEL架构,Docker离线安装需依赖rpm包,优先选择适配对应架构的官方稳定版本(本文以Docker 20.10.24为例,分arm64、x86_64两个版本)。

操作步骤(分架构执行):

  1. 根据目标服务器架构,访问对应Docker官方rpm仓库,下载以下3个核心rpm包(后缀对应架构):

    1. arm64架构:访问 https://download.docker.com/linux/centos/8/aarch64/stable/Packages/,下载 aarch64 后缀的包:

      • containerd.io-xxx.aarch64.rpm(容器运行时依赖);

      • docker-ce-xxx.aarch64.rpm(Docker核心包);

      • docker-ce-cli-xxx.aarch64.rpm(Docker命令行工具)。

    2. x86_64架构:访问 https://download.docker.com/linux/centos/8/x86_64/stable/Packages/,下载 x86_64 后缀的包:

      • containerd.io-xxx.x86_64.rpm(容器运行时依赖);

      • docker-ce-xxx.x86_64.rpm(Docker核心包);

      • docker-ce-cli-xxx.x86_64.rpm(Docker命令行工具)。

  2. 将下载的3个rpm包放在同一目录(如 /home/docker_offline),并打包为tar压缩包(标注架构,方便后续区分),方便后续传输:

    1. arm64架构:tar -czvf docker_arm64_20.10.24.tar.gz /home/docker_offline/*.rpm

    2. x86_64架构:tar -czvf docker_x86_64_20.10.24.tar.gz /home/docker_offline/*.rpm

注意:务必确认rpm包后缀与目标服务器架构一致(arm64对应aarch64,x86_64对应x86_64),否则无法在麒麟V10上安装。

2.2 下载并打包Redis对应架构Docker镜像

Redis官方镜像包含多个架构版本,需根据目标服务器架构(arm64/x86_64),精准获取对应版本,避免下载错误架构导致无法运行。本文以Redis 6.2.6(长期支持版,稳定可靠,适配两种架构)为例,仅作为Docker环境验证工具,无需复杂配置。

操作步骤(分架构执行):

  1. 拉取正确架构的Redis镜像:

    1. 若中转站机器与目标服务器同架构(arm64/x86_64),直接拉取,Docker会自动匹配架构:docker pull redis:6.2.6

    2. 若中转站机器与目标服务器架构不同,必须指定对应平台,否则会拉取错误架构版本:

      • 目标服务器为arm64:docker pull --platform linux/arm64 redis:6.2.6

      • 目标服务器为x86_64:docker pull --platform linux/amd64 redis:6.2.6

  2. 验证镜像架构(确保与目标服务器一致): docker inspect redis:6.2.6 | grep Architecture输出结果:arm64架构对应 “Architecture”: “aarch64”,x86_64架构对应 “Architecture”: “amd64”,确认镜像正确。

  3. 打包镜像为tar文件(标注架构),便于内网传输:

    1. arm64架构: # 查看镜像ID(假设为 abcdef123456) docker images | grep redis # 打包镜像 docker save abcdef123456 -o /home/redis_arm64_6.2.6.tar

    2. x86_64架构: # 查看镜像ID(假设为 abcdef123456) docker images | grep redis # 打包镜像 docker save abcdef123456 -o /home/redis_x86_64_6.2.6.tar

  4. (可选)校验文件完整性:计算tar包的哈希值,后续传输到内网后核对,避免文件损坏:

    1. arm64架构:sha256sum /home/redis_arm64_6.2.6.tar > redis_sha256.txt

    2. x86_64架构:sha256sum /home/redis_x86_64_6.2.6.tar > redis_sha256.txt

至此,外网准备工作完成。根据目标服务器架构,将对应的Docker离线包、Redis镜像包(及redis_sha256.txt)通过安全介质,传输到内网麒麟V10目标服务器的 /home 目录下。

三、第二步:内网部署——麒麟V10安装Docker(分架构)

目标服务器为无外网权限的麒麟V10(arm64/x86_64),需根据架构,通过对应离线rpm包安装Docker,步骤如下(分架构执行):

3.1 解压Docker离线包(分架构)

1
2
3
4
5
6
cd /home
# 分架构解压,释放rpm文件
# arm64架构
tar -xzvf docker_arm64_20.10.24.tar.gz
# x86_64架构
tar -xzvf docker_x86_64_20.10.24.tar.gz

3.2 离线安装Docker依赖与核心包(分架构)

进入解压后的rpm包目录,执行安装命令(需root权限,若提示依赖缺失,需提前准备对应离线依赖包,依赖包架构需与目标服务器一致):

1
2
3
4
5
6
cd /home/docker_offline
# 安装3个rpm包(顺序无关,--nodeps可忽略依赖检查,若有依赖需提前安装)
# arm64架构
rpm -ivh --nodeps containerd.io-xxx.aarch64.rpm docker-ce-cli-xxx.aarch64.rpm docker-ce-xxx.aarch64.rpm
# x86_64架构
rpm -ivh --nodeps containerd.io-xxx.x86_64.rpm docker-ce-cli-xxx.x86_64.rpm docker-ce-xxx.x86_64.rpm

3.3 启动Docker并设置开机自启

1
2
3
4
5
6
7
8
# 启动Docker服务
systemctl start docker
# 设置开机自启(关键,避免服务器重启后Docker失效)
systemctl enable docker
# 验证Docker安装成功
docker --version
# 查看Docker运行状态(Active: active (running) 即为正常)
systemctl status docker | grep Active

若启动失败,大概率是依赖缺失或rpm包架构错误。可执行 systemctl status docker 查看错误日志,补充对应依赖或重新下载正确架构的rpm包。

第三步:内网部署——Docker加载并运行Redis(环境验证,分架构)

Docker安装完成后,即可加载提前传输的对应架构Redis镜像,启动容器并完成简单验证,确认Docker环境正常可用(Redis仅作为验证工具,无需复杂配置)。

3.1 导入Redis镜像(分架构)

将传输到 /home 目录的对应架构Redis镜像包导入Docker本地仓库:

1
2
3
4
5
6
7
8
cd /home
# 分架构导入镜像
# arm64架构
docker load -i redis_arm64_6.2.6.tar
# x86_64架构
docker load -i redis_x86_64_6.2.6.tar
# (可选)核对文件完整性,确保传输未损坏
sha256sum -c redis_sha256.txt

导入成功后,执行 docker images 会看到一个无标签(REPOSITORY和TAG为 )的镜像,需为其打上易识别的标签(标注架构),方便后续管理:

1
2
3
4
5
6
7
8
9
# 查看镜像ID(假设为 1234567890ab)
docker images
# 分架构打标签(格式:镜像名:版本-架构)
# arm64架构
docker tag 1234567890ab redis:6.2.6-arm64
# x86_64架构
docker tag 1234567890ab redis:6.2.6-x86_64
# 验证标签
docker images | grep redis

3.2 配置Redis数据持久化(可选,验证环境可省略)

若仅作为环境验证,可省略数据持久化;若需简单留存数据,可将Redis数据目录挂载到宿主机(生产环境建议配置,验证环境可跳过):

1
2
3
4
5
6
# 创建宿主机数据目录(验证环境可省略)
sudo mkdir -p /data/redis/data
# 赋予目录读写权限
sudo chmod 755 /data/redis
# 进阶:修改目录所属用户(匹配容器内默认用户,避免权限报错)
sudo chown -R 999:999 /data/redis/data

3.3 启动Redis容器(环境验证版,分架构)

使用 docker run 命令启动容器,配置端口映射、自启等基础参数,确保容器正常运行,用于验证Docker环境可用(无需复杂配置,仅开启基础服务):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 分架构启动容器,验证环境无需复杂配置
# arm64架构
docker run -d \
--name redis-server \ # 容器名称,便于管理
--restart=always \ # 容器随Docker自启,保障可用性
-p 6379:6379 \ # 端口映射:宿主机6379端口映射到容器6379端口(Redis默认端口)
# 验证环境可省略数据挂载,如需挂载添加以下一行
# -v /data/redis/data:/data \
redis:6.2.6-arm64 \ # 镜像标签(arm64架构)
--requirepass "RedisTest123!" # 简单设置密码,避免未授权访问(验证环境可选)

# x86_64架构
docker run -d \
--name redis-server \ # 容器名称,便于管理
--restart=always \ # 容器随Docker自启,保障可用性
-p 6379:6379 \ # 端口映射:宿主机6379端口映射到容器6379端口(Redis默认端口)
# 验证环境可省略数据挂载,如需挂载添加以下一行
# -v /data/redis/data:/data \
redis:6.2.6-x86_64 \ # 镜像标签(x86_64架构)
--requirepass "RedisTest123!" # 简单设置密码,避免未授权访问(验证环境可选)

参数说明(验证环境重点):

  • -d:后台运行容器,不占用终端;

  • –restart=always:服务器重启、Docker重启后,容器自动启动,便于长期验证;

  • -p 6379:6379:Redis默认端口映射,便于后续连接验证;

  • –requirepass:简单设置密码,避免内网未授权访问(验证环境可选,提升安全性)。

启动后,验证容器状态(确认Docker环境正常):

1
docker ps | grep redis-server

若 STATUS 为 Up,说明Redis容器已成功运行,Docker环境部署正常,达到环境验证目的。

四、第四步:环境验证——Redis连接测试(核心,确认环境可用)

Redis容器启动后,通过简单的连接命令,验证Docker容器运行正常、Redis服务可用,完成整个环境验证流程。

4.1 容器内连接Redis测试

1
2
3
4
5
6
7
8
# 进入Redis容器内部
docker exec -it redis-server /bin/bash
# 连接Redis(若设置了密码,需输入密码)
redis-cli
# 输入密码(若设置了--requirepass参数)
auth RedisTest123!
# 执行简单命令,验证服务可用
ping

若输出 PONG,说明Redis服务正常运行,Docker容器部署无误,环境验证通过。

4.2 宿主机连接Redis测试(可选)

1
2
3
4
5
# 宿主机直接连接Redis(需确保宿主机安装了redis-cli,或使用Docker命令间接连接)
# 方法1:使用Docker命令间接连接
docker exec -it redis-server redis-cli -a RedisTest123! ping
# 方法2:宿主机安装redis-cli后直接连接
redis-cli -h 127.0.0.1 -p 6379 -a RedisTest123! ping

若输出 PONG,说明宿主机与容器通信正常,整个Docker+Redis环境验证完成。

4.3 常见验证失败排查

  • 容器启动失败:检查镜像架构是否与目标服务器一致,执行 docker logs redis-server 查看错误日志;

  • ping命令无响应:检查容器是否正常运行(docker ps),密码是否输入正确,防火墙是否开放6379端口;

  • 防火墙开放端口(若无法连接):sudo firewall-cmd --zone=public --add-port=6379/tcp --permanent sudo firewall-cmd --reload

五、安全加固与环境清理(验证环境可选)

若仅作为临时环境验证,使用完成后可清理容器和镜像,释放服务器资源;若需长期保留,可进行简单安全加固。

5.1 环境清理(临时验证用)

1
2
3
4
5
6
7
# 停止Redis容器
docker stop redis-server
# 删除Redis容器
docker rm redis-server
# 删除Redis镜像(按需执行)
# arm64架构:docker rmi redis:6.2.6-arm64
# x86_64架构:docker rmi redis:6.2.6-x86_64

5.2 简单安全加固(长期保留用)

  • 端口限制:通过防火墙限制仅允许内部指定IP访问6379端口,避免内网未授权访问;

  • 密码加固:设置复杂密码,避免简单密码泄露;

  • 容器资源限制:若长期保留,可限制容器内存和CPU使用,避免占用过多服务器资源: # 启动容器时添加资源限制参数(以arm64为例,x86_64替换镜像标签即可) docker run -d \ --name redis-server \ --restart=always \ -p 6379:6379 \ --memory="1g" \ # 限制容器最大使用1GB内存 --cpus="1.0" \ # 限制容器最大使用1个CPU核心 redis:6.2.6-arm64 --requirepass "RedisTest123!"

六、总结

本文完整覆盖了麒麟V10(arm64/x86_64两种架构)内网环境下,Docker离线安装、Redis(环境验证版)离线部署与测试的全流程,核心要点是“架构匹配”和“环境验证”——确保Docker安装包、Redis镜像均与目标服务器架构一致,提前在外网完成资源准备,内网仅需执行安装、加载、启动和简单测试,即可完成Docker环境验证。

整个流程避开了源码编译、依赖冲突等坑点,通过Docker容器化方式,保证了环境一致性和部署便捷性,适用于政企单位、涉密项目等无外网场景的Docker环境验证。Redis仅作为验证工具,配置简单、操作便捷,能快速确认Docker环境部署成功;若需部署其他Docker应用,可参考本文“外网准备-内网部署”的思路,替换对应镜像即可。

若实际操作中遇到问题,优先查看容器日志和系统日志,大部分错误均可通过日志定位并解决;两种架构的核心操作一致,仅需注意安装包和镜像的架构区分,新手也能轻松落地。

Label Studio是一款开源、多模态的数据标注工具,支持图像、文本、视频、音频等多种数据类型的标注任务,广泛应用于机器学习数据预处理场景。本文基于Docker容器化技术,详细讲解Label Studio的私有部署流程,整合Nginx反向代理、外部Redis缓存、外部PostgreSQL数据库配置,实现安全、高效、可协作的私有标注平台搭建,适配企业内网多人协作、敏感数据私有化存储的核心需求。

一、部署背景与核心需求

在实际企业应用中,数据标注往往涉及敏感信息(如隐私图片、内部文档),因此私有化部署是必然选择。本次部署需满足以下核心需求:

  • 私有化部署:所有标注数据、配置信息存储在内网,杜绝数据外流;

  • 多人协作:支持多角色用户(管理员、标注员、审核员)权限管理,实现数据集隔离;

  • 安全访问:隐藏Label Studio核心服务端口,通过Nginx反向代理对外暴露访问入口;

  • 稳定可靠:复用宿主机外部Redis(缓存)和PostgreSQL(数据库),提升服务稳定性和可维护性;

  • 易用性:简化访问方式,用户无需记忆复杂端口,通过固定端口即可访问。

二、前置条件准备

部署前需确保服务器满足以下环境要求,避免因依赖缺失导致部署失败:

2.1 环境版本要求

环境/工具 版本要求 检查与安装命令
操作系统 Linux(Ubuntu 20.04+/CentOS 7+) 无(推荐Ubuntu,兼容性更好)
Docker 20.10+ 检查:docker -v;安装:curl -fsSL https://get.docker.com | sh
Docker Compose V2+ 检查:docker compose version;安装:apt install docker-compose-plugin(Ubuntu)
Nginx 1.18+ 检查:nginx -v;安装:apt install nginx(Ubuntu)/ yum install nginx(CentOS)
PostgreSQL 13+/14+ 部署在宿主机,检查:systemctl status postgresql
Redis 6+/7+ 部署在宿主机,检查:systemctl status redis
端口 8081(Nginx对外)、9000(Label Studio本机)、5432(PG)、6379(Redis) 检查端口占用:netstat -tulpn | grep 端口号

2.2 核心前置配置(关键!)

本次部署使用宿主机已有的Redis和PostgreSQL(无自定义Docker Network),需提前配置两者允许容器访问,否则会出现连接失败问题。

2.2.1 宿主机PostgreSQL配置

  1. 登录PostgreSQL,创建Label Studio专属数据库和用户(用于容器连接): -- 创建数据库 CREATE DATABASE label_studio; -- 创建用户(替换your_pg_pass为自定义密码) CREATE USER label_studio WITH PASSWORD 'your_pg_pass'; -- 赋予用户数据库全权限 GRANT ALL PRIVILEGES ON DATABASE label_studio TO label_studio;

  2. 修改PostgreSQL配置文件,允许容器访问:

    1. 编辑postgresql.conf(路径通常为/etc/postgresql/版本/main/postgresql.conf): listen_addresses = '*' # 允许所有IP访问(内网环境安全)

    2. 编辑pg_hba.conf(同上述路径),添加容器网段和宿主机授权: # 允许Docker容器网段(默认172.17.0.0/16)访问 host label_studio label_studio 172.17.0.0/16 md5 # 允许宿主机本地访问 host label_studio label_studio 127.0.0.1/32 md5

  3. 重启PostgreSQL服务:systemctl restart postgresql

2.2.2 宿主机Redis配置

  1. 编辑Redis配置文件redis.conf(路径通常为/etc/redis/redis.conf): bind 0.0.0.0 # 允许所有IP访问(内网环境) protected-mode no # 关闭保护模式,允许容器访问 requirepass your_redis_pass # 配置密码(可选,推荐,替换为自定义密码)

  2. 重启Redis服务:systemctl restart redis

三、分步部署Label Studio(Docker+Nginx)

本次部署采用Docker Compose管理容器,整合Label Studio和Nginx两个核心服务,Label Studio仅绑定本机端口,通过Nginx反向代理对外暴露8081端口,确保服务安全。

3.1 创建部署目录(统一管理文件)

为避免配置文件混乱,创建统一的部署目录,所有数据、配置文件均存储在此目录下:

1
2
3
4
# 创建核心部署目录
mkdir -p /opt/label-studio/{data,conf,nginx}
# 进入部署目录
cd /opt/label-studio

目录说明:

  • data:存储Label Studio标注数据、日志等(持久化);

  • conf:存储Label Studio配置文件;

  • nginx:存储Nginx反向代理配置文件和缓存文件。

3.2 编写Docker Compose配置文件

/opt/label-studio目录下创建docker-compose.yml文件,核心配置如下(已整合所有需求,可直接复制使用,需替换自定义密码和宿主机IP):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
version: '3.8'

services:
# Label Studio 核心服务(仅本机可访问,绑定9000端口)
label-studio:
image: heartexlabs/label-studio:latest
container_name: label-studio
restart: always
environment:
# 连接宿主机PostgreSQL配置
- LABEL_STUDIO_DB=postgres
- LABEL_STUDIO_DB_HOST=192.168.1.100 # 替换为宿主机内网IP
- LABEL_STUDIO_DB_PORT=5432
- LABEL_STUDIO_DB_USER=label_studio
- LABEL_STUDIO_DB_PASSWORD=your_pg_pass # 替换为之前创建的PG密码
- LABEL_STUDIO_DB_NAME=label_studio
# 连接宿主机Redis配置
- REDIS_HOST=192.168.1.100 # 替换为宿主机内网IP
- REDIS_PORT=6379
- REDIS_PASSWORD=your_redis_pass # 替换为Redis密码,无则删除该行
- REDIS_DB=0
# 基础配置
- ALLOWED_HOSTS=*
- LABEL_STUDIO_DATA_DIR=/label-studio/data
volumes:
- ./data:/label-studio/data
- ./conf:/label-studio/conf
# 核心:仅绑定本机127.0.0.1:9000,外部无法直接访问
ports:
- 127.0.0.1:9000:8080
# Linux系统兼容host.docker.internal(可选,用于容器访问宿主机)
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- label-studio-net

# Nginx 反向代理服务(对外暴露8081端口)
nginx:
image: nginx:1.23-alpine
container_name: label-studio-nginx
restart: always
ports:
- "8081:80" # 宿主机8081端口映射到容器80端口(对外访问入口)
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx/cache:/var/cache/nginx
depends_on:
- label-studio # 确保Label Studio启动后再启动Nginx
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- label-studio-net

# 容器内部网络(仅用于Label Studio和Nginx通信)
networks:
label-studio-net:
driver: bridge

3.3 编写Nginx反向代理配置

/opt/label-studio/nginx目录下创建nginx.conf文件,核心作用是将外部8081端口的请求,代理到本机9000端口的Label Studio服务,同时优化访问性能和大文件上传体验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
server {
listen 80; # 容器内部监听80端口,与Docker映射对应
server_name _; # 匹配所有IP,无需配置域名

# 支持大文件上传(标注视频/图片常用,可根据需求调整大小)
client_max_body_size 10G;

# 传递真实访问IP和请求信息,便于日志排查
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# 超时配置,避免大文件上传超时
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;

# 核心反向代理逻辑:将请求转发到本机9000端口(Label Studio)
location / {
proxy_pass http://127.0.0.1:9000;
proxy_redirect off;
}

# 静态文件缓存(CSS/JS/图片等),提升访问速度
location /static/ {
proxy_pass http://127.0.0.1:9000/static/;
expires 30d; # 缓存30天
add_header Cache-Control "public, max-age=2592000";
}
}

3.4 启动服务并验证

  1. 启动容器服务(后台运行): docker compose up -d

  2. 检查服务运行状态(确保所有容器均为Up状态): docker compose ps若出现Exited状态,可通过日志排查问题:docker compose logs -f label-studio

  3. 访问验证:

    1. 本机测试:curl http://127.0.0.1:9000,应返回Label Studio登录页面HTML内容;

    2. 外部/内网测试:浏览器打开http://宿主机IP:8081(如http://192.168.1.100:8081),进入Label Studio登录页面,说明部署成功;

    3. 安全验证:外部机器访问http://宿主机IP:9000,应无法访问(符合预期,确保Label Studio不对外暴露)。

  4. 首次登录配置: 首次访问http://宿主机IP:8081,会引导创建超级管理员账号(用户名、密码、邮箱),记住该账号,用于后续用户管理和项目配置。

四、关键配置说明与优化

4.1 端口安全设计(核心亮点)

本次部署采用“Label Studio本机隐藏+Nginx反向代理”的安全架构,核心设计如下:

  • Label Studio端口映射为127.0.0.1:9000:8080,仅宿主机本机可直接访问,外部无法直接发起请求,避免核心服务被恶意攻击;

  • Nginx对外仅暴露8081端口,所有用户通过该端口访问,由Nginx统一转发请求,相当于给Label Studio加了一层“防护屏障”;

  • 防火墙仅需放行8081端口,无需放行9000、5432、6379端口,进一步提升内网安全性。

4.2 外部Redis/PG连接说明

本次部署未使用Docker内置的Redis和PostgreSQL,而是复用宿主机已有的实例,优势如下:

  • 统一管理:企业内网通常已有数据库/缓存集群,复用现有实例可减少资源浪费,便于统一运维和数据备份;

  • 数据安全:数据库和缓存数据存储在宿主机,而非Docker容器,避免容器删除导致数据丢失;

  • 灵活扩展:后续可根据需求,将Redis/PG替换为集群架构,无需修改Label Studio配置。

注意:配置中LABEL_STUDIO_DB_HOSTREDIS_HOST必须填写宿主机内网IP,不可使用localhost(容器内localhost指向自身,而非宿主机)。

4.3 性能优化配置

  • 大文件上传:Nginx配置client_max_body_size 10G,支持GB级视频、图片上传,避免上传超时;

  • 静态文件缓存:Nginx对Label Studio的静态资源(CSS/JS/图片)进行30天缓存,减少重复请求,提升页面加载速度;

  • 超时配置:延长代理超时时间至300s,适配标注过程中长时间连接的场景。

五、多人权限管理与数据集导入

5.1 多角色用户管理

登录超级管理员账号后,可创建多角色用户,实现多人协作和数据集隔离:

  1. 点击顶部导航栏「Settings」→「Users」;

  2. 点击「Create User」,填写用户名、密码,选择角色:

    1. Annotator(标注员):仅能标注分配的任务,无审核和管理权限;

    2. Reviewer(审核员):可标注并审核标注结果,确保标注质量;

    3. Admin(管理员):全权限,可管理用户、项目和配置(谨慎分配)。

  3. 为用户分配项目权限:进入目标项目→「Settings」→「Access」,添加用户并分配View(仅查看)、Edit(可标注)、Owner(项目所有者)权限。

5.2 数据集导入(私有化存储)

Label Studio支持多种数据集导入方式,适配不同规模的标注需求,所有数据均存储在宿主机/opt/label-studio/data目录,确保私有化:

  • 小数据集:进入项目→「Import」→「Upload Files」,批量上传本地文件(图片、文本、视频等);

  • 大数据集(推荐):将内网数据集文件夹挂载到容器,避免直接上传耗时: # 1. 创建内网数据集目录 mkdir -p /data/label-dataset # 2. 修改docker-compose.yml,添加卷挂载(在volumes下新增) volumes: - ./data:/label-studio/data - ./conf:/label-studio/conf - /data/label-dataset:/label-studio/dataset # 3. 重启服务 docker compose down && docker compose up -d导入时选择「Local Storage」,找到/label-studio/dataset目录即可批量导入。

六、常用运维命令与故障排查

6.1 常用运维命令

操作需求 命令
启动服务 docker compose up -d
停止服务 docker compose down
重启服务 docker compose restart
查看Label Studio日志 docker compose logs -f label-studio
查看Nginx日志 docker compose logs -f nginx
备份数据(核心) cp -r /opt/label-studio/data /backup/label-studio-$(date +%Y%m%d)
重置管理员密码 docker exec -it label-studio python manage.py changepassword 用户名

6.2 常见故障排查

  • 问题1:Label Studio启动失败,日志提示“could not connect to postgres/redis” 排查:① 确认宿主机Redis/PG服务正在运行;② 检查配置文件中IP、端口、密码是否正确;③ 确认宿主机防火墙放行5432、6379端口;④ 检查PG/Redis配置是否允许外部访问。

  • 问题2:访问http://宿主机IP:8081无法打开页面 排查:① 检查Nginx和Label Studio容器是否正常运行;② 确认宿主机防火墙放行8081端口;③ 查看Nginx日志,确认反向代理配置是否正确。

  • 问题3:大文件上传失败,提示超时 排查:修改Nginx配置中的client_max_body_sizeproxy_read_timeout参数,增大数值后重启Nginx。

七、总结

本文基于Docker容器化技术,完成了Label Studio私有标注平台的完整部署,整合了Nginx反向代理、外部Redis缓存、外部PostgreSQL数据库,实现了以下核心目标:

  • 私有化安全:所有数据存储在内网,Label Studio仅本机可访问,通过Nginx反向代理对外暴露,杜绝数据外流和恶意攻击;

  • 多人协作:支持多角色用户管理和数据集隔离,适配企业团队标注场景;

  • 稳定高效:复用宿主机现有Redis/PG,优化大文件上传和页面加载性能,适合长期使用;

  • 易用易维护:通过Docker Compose统一管理容器,运维命令简洁,故障排查便捷。

本部署方案适用于企业内网、敏感数据标注场景,可根据实际需求扩展功能(如配置HTTPS加密、集成MinIO私有存储、批量导入/导出标注数据),进一步提升平台的安全性和实用性。

一、ssh -N -L 实现内网端口访问的原理与用法

ssh -N -L 是 SSH 本地端口转发(Local Port Forwarding)的核心命令,其核心作用是在本地机器的指定端口远程服务器可访问的内网目标端口之间,建立一条加密传输隧道,从而实现通过访问本地端口,间接访问内网服务的目的,无需直接暴露内网地址。

1. 命令参数解释

明确各参数的具体含义,是正确使用该命令、排查问题的基础,核心参数及常用补充参数如下:

  • -L:本地端口转发的核心参数,格式固定为 本地端口:内网目标地址:内网目标端口,用于指定本地端口、内网目标的地址和端口。

  • -N:仅建立 SSH 隧道,不执行远程服务器的任何命令,避免不必要的 shell 登录,专注于端口转发功能。

  • 补充常用参数(提升使用便捷性和安全性):

  • -f:将 SSH 隧道后台运行,避免终端关闭后隧道断开,无需一直保持终端窗口打开。

  • -i:指定 SSH 私钥文件路径,适用于密钥登录场景,无需手动输入密码,提升登录效率和安全性。

2. 典型场景与完整命令

结合实际网络拓扑,给出最常用的应用场景,方便读者直接参考套用:

  • 本地机器:可正常访问公网,能连接公网服务器(IP:203.0.113.10,默认 SSH 端口 22)。

  • 公网服务器:具备内网访问权限,可连通内网目标机器(IP:192.168.1.50,内网 Web 服务端口 8080)。

  • 核心目标:在本地机器上,访问内网 192.168.1.50:8080 对应的服务。

完整命令示例(推荐后台运行,避免终端占用):

1
2
# 后台建立加密隧道,将本地8888端口转发至内网192.168.1.50:8080
ssh -f -N -L 8888:192.168.1.50:8080 root@203.0.113.10

3. 访问内网端口的步骤

  1. 执行转发命令

在本地终端运行上述命令后,SSH 会自动在本地监听 8888 端口,此时输入公网服务器登录密码(或通过密钥自动认证),即可完成隧道建立,无需额外操作。

  • 密钥登录场景(补充 -i 参数,替换私钥文件路径):
  1. 访问内网服务

隧道建立成功后,无需额外配置,在本地浏览器或对应客户端中,访问 http://localhost:8888(或 127.0.0.1:8888),即可等同于直接访问内网的 192.168.1.50:8080 服务。

4. 进阶场景:多层内网转发

若公网服务器与内网目标机器之间存在多层网络(如公网服务器仅能访问内网网关 192.168.1.1,而网关可访问深层内网 192.168.2.10:3306,如 MySQL 数据库端口),命令格式无需调整,仅需修改内网目标地址即可:

1
2
# 本地3306端口转发至深层内网192.168.2.10:3306(MySQL端口)
ssh -f -N -L 3306:192.168.2.10:3306 root@203.0.113.10

此时本地连接 MySQL 数据库时,只需将连接地址设为 localhost,端口设为 3306,即可正常访问内网数据库。

5. 关闭隧道(可选)

若无需继续使用隧道,可通过以下命令关闭,避免占用本地端口和网络资源:

1
2
3
4
# 查找SSH隧道对应的进程(筛选出目标隧道进程)
ps aux | grep "ssh -f -N -L"
# 杀死对应进程(将12345替换为实际查询到的进程PID)
kill -9 12345

二、注意事项

  1. 本地端口不可占用:若本地指定的端口(如示例中的 8888)已被其他程序占用,会提示bind: Address already in use,只需更换一个未被占用的本地端口(如 8889)即可。

  2. 公网服务器需连通内网目标:公网服务器必须能正常 ping 通内网目标地址(如 192.168.1.50),且内网目标端口未被防火墙拦截,否则隧道虽能建立,但无法正常访问内网服务。

  3. 权限需达标:SSH 登录公网服务器的用户(如示例中的 root)需具备足够的操作权限,同时内网服务需允许公网服务器的 IP 访问,否则会出现权限不足或访问被拒绝的问题。

总结

  1. ssh -N -L 命令的核心逻辑的是 本地端口:内网目标地址:内网目标端口 远程服务器地址,建议搭配 -f 参数后台运行,提升使用便捷性。

  2. 隧道建立后,访问localhost:本地端口,即可间接访问内网目标服务,无需直接接触内网环境。

  3. 使用该命令的关键前提:公网服务器能正常连通内网目标的地址和端口,且本地指定的转发端口未被占用。

ZFS(Zettabyte File System)作为一款融合文件系统、卷管理与快照功能的先进存储解决方案,凭借写时复制、校验和自动修复、自适应缓存等核心特性,成为构建高可靠、可扩展存储系统的首选。本文结合Debian与Ubuntu 20.04系统,从安装部署、核心配置、性能优化到运维管理,全面讲解ZFS的实战应用,助力搭建适配数据库、虚拟化、大数据等场景的高性能存储体系。

一、ZFS核心价值与部署前提

1. 核心优势

ZFS解决了传统文件系统与RAID的诸多痛点:

  • 数据完整性:全量数据与元数据校验和(支持SHA-256),自动检测并修复静默数据损坏;

  • 写时复制(CoW):修改数据不覆盖原数据,避免写入中断导致的损坏;

  • 灵活冗余:RAID-Z1/2/3(对应1/2/3块盘冗余)优于传统RAID-5/6,无“写孔”问题;

  • 高效缓存:ARC(自适应替换缓存)利用系统内存大幅提升读性能;

  • 便捷管理:秒级快照、在线扩容、热备盘自动替换,降低运维成本。

2. 硬件与系统要求

  • 内存:至少4GB(推荐32GB以上),ECC内存优先(降低内存错误导致的数据损坏风险);

  • 存储设备:NVMe SSD/企业级HDD,建议同规格磁盘组(避免性能不均);

  • 系统版本:Debian 11+/Ubuntu 20.04+(内置ZFS支持,无需第三方源)。

二、ZFS快速部署(Debian/Ubuntu通用)

1. 安装ZFS工具链

1
2
3
4
5
6
7
8
9
10
# 启用Debian contrib/non-free仓库(Debian专属)
echo "deb http://deb.debian.org/debian $(lsb_release -sc) main contrib non-free non-free-firmware" | sudo tee -a /etc/apt/sources.list

# 更新并安装ZFS
sudo apt update
sudo apt install -y zfsutils-linux zfs-dkms linux-headers-$(uname -r)

# 验证安装
sudo modprobe zfs
zfs --version && zpool --version

2. 存储池规划与创建

存储池(ZPool)是ZFS的核心载体,由虚拟设备(VDev)组成,推荐结合业务需求选择冗余策略:

冗余类型 最小磁盘数 故障容忍数 适用场景
RAID-Z1 3 1 个人/小型存储,成本敏感
RAID-Z2 4 2 企业级核心数据,高可靠性要求
Mirror 2 1 系统盘/关键小容量数据

实战示例:8块4TB NVMe构建RAID-Z2池(双冗余,高并发场景)

1
2
3
4
5
6
7
8
9
# 推荐使用by-id路径(避免设备名变动)
sudo zpool create -o ashift=12 \
tank \
raidz2 /dev/disk/by-id/nvme-XXX1 /dev/disk/by-id/nvme-XXX2 /dev/disk/by-id/nvme-XXX3 /dev/disk/by-id/nvme-XXX4 \
raidz2 /dev/disk/by-id/nvme-XXX5 /dev/disk/by-id/nvme-XXX6 /dev/disk/by-id/nvme-XXX7 /dev/disk/by-id/nvme-XXX8

# 查看池状态(核心运维命令)
sudo zpool status
sudo zpool list
  • ashift=12:适配4KB扇区磁盘,提升IO效率;

  • 两组RAID-Z2 vdev设计:兼顾并行性能与冗余,可用空间约24TB。

三、核心配置:性能与可靠性双优化

1. 基础属性调优

1
2
3
4
5
6
7
8
9
10
11
12
# 启用lz4压缩(性能损失<1%,压缩比约2:1)
sudo zfs set compression=lz4 tank

# 增强数据校验(默认启用,升级为SHA-256)
sudo zfs set checksum=sha256 tank

# 关闭atime(减少无谓I/O)
sudo zfs set atime=off tank

# 限制ARC缓存(设为系统内存50%,示例:64GB内存→32GB=34359738368字节)
echo "options zfs zfs_arc_max=34359738368" | sudo tee /etc/modprobe.d/zfs.conf
sudo update-initramfs -u

2. 按场景调整记录大小(Recordsize)

Recordsize是ZFS数据块大小,直接影响不同负载的性能:

1
2
3
4
5
# 数据库场景(MySQL/PostgreSQL,匹配16KB页大小)
sudo zfs set recordsize=16K tank/db

# 大文件存储(视频/备份,提升吞吐量)
sudo zfs set recordsize=1M tank/media

3. 快照与备份策略

ZFS快照基于写时复制,几乎无性能开销,建议自动化管理:

1
2
3
4
5
6
7
8
9
10
# 创建初始快照
sudo zfs snapshot tank@initial

# 配置每日自动快照(crontab)
sudo crontab -e
# 添加以下行:每日2点创建快照,命名格式为日期
0 2 * * * /sbin/zfs snapshot tank@daily-$(date +\%F)

# 跨服务器备份快照(通过SSH)
zfs send tank@daily-20240501 | ssh user@remote-host zfs receive remote-tank/backup@20240501

四、日常运维与故障处理

1. 关键运维命令

操作 命令示例
查看池状态 sudo zpool status tank
数据校验(修复损坏) sudo zpool scrub tank
替换故障盘 sudo zpool replace tank 旧盘ID 新盘ID
在线扩容 sudo zpool add tank raidz2 新盘1 新盘2 …
删除无用快照 sudo zfs destroy tank@daily-20240401

2. 常见问题解决

  • 存储池导入失败:系统迁移后设备路径变动,通过by-id重新扫描:

    1
    2
    sudo zpool import -d /dev/disk/by-id  # 列出可导入的池
    sudo zpool import -f tank # 强制导入(无数据冲突时)
  • RAID-Z重建速度慢:降低系统I/O压力,启用自动替换:

    1
    sudo zpool set autoreplace=on tank
  • 压缩比过低:数据不可压缩(如已加密文件),可切换zstd算法或调整应用层数据格式:

    1
    sudo zfs set compression=zstd tank

五、最佳实践总结

  1. 硬件选型:ECC内存+NVMe SSD/同规格HDD,存储池使用率控制在80%以内;

  2. 冗余策略:核心数据用RAID-Z2,系统盘用Mirror,配备热备盘;

  3. 性能调优:按负载调整recordsize,启用lz4压缩,合理配置ARC缓存;

  4. 运维规范:每月执行zpool scrub,保留30天快照,定期跨机备份;

  5. 设备标识:始终使用/dev/disk/by-id路径,避免设备名变动导致故障。

ZFS凭借强大的原生能力,无需额外工具即可实现“高性能+高可靠+易管理”的存储架构。无论是Debian还是Ubuntu系统,遵循本文的部署与优化策略,可快速搭建适配企业级数据库、虚拟化、大数据分析等场景的存储系统,显著降低数据丢失风险与运维成本。

0%