# CloudNest(云巢)— 轻量级个人云存储平台部署指南 > **项目定位**:用 Golang 构建的个人云存储平台,学习 K8s 编排、微服务架构、分布式存储的实战项目。 > **服务器要求**:2核 CPU / 2GB 内存 / 40GB 硬盘 / 有域名 > **技术栈**:Go + Gin + MySQL + Redis + MinIO + k3s + Docker --- ## 一、项目架构总览 ``` ┌─────────────────────────────────┐ │ 域名 (yourdomain.com) │ └──────────────┬──────────────────┘ │ ┌──────────────▼──────────────────┐ │ Traefik Ingress (k3s自带) │ │ - SSL自动证书(Let's Encrypt) │ │ - 路由分发 │ └──┬───────────────────┬──────────┘ │ │ ┌──────────▼─────┐ ┌────────▼──────────┐ │ Auth Service │ │ File Service │ │ (认证服务) │ │ (文件服务) │ │ 端口: 8081 │ │ 端口: 8082 │ └──────┬──────────┘ └──┬────────┬───────┘ │ │ │ ┌────────▼──────┐ ┌─────▼───┐ ┌──▼──────────┐ │ MySQL │ │ Redis │ │ MinIO │ │ (用户数据) │ │ (缓存) │ │ (对象存储) │ └───────────────┘ └─────────┘ └─────────────┘ ``` ### 微服务划分 | 服务 | 职责 | 端口 | |------|------|------| | **Auth Service** | 用户注册/登录、JWT 签发 | 8081 | | **File Service** | 文件上传/下载/列表/删除 | 8082 | | **Traefik** | 反向代理 + SSL + 路由 | 80/443 | | **MinIO** | S3 兼容对象存储(分布式存储学习核心) | 9000 | ### 资源预估(2GB 内存可承受) | 组件 | 内存限制 | 说明 | |------|---------|------| | k3s + Traefik | ~350MB | K8s 核心 | | MySQL | 128MB | 用户数据 | | Redis | 64MB | 会话缓存 | | MinIO | 256MB | 对象存储 | | Auth Service | 64MB | Go 编译后内存占用极低 | | File Service | 64MB | Go 编译后内存占用极低 | | 系统 + Swap | ~600MB | 操作系统 + 2GB Swap 缓冲 | | **合计** | **~1.6GB** | 留有 400MB 余量 | --- ## 二、服务器环境搭建(从头开始) ### 2.1 系统初始化 ```bash # ===== 1. 更新系统 ===== sudo apt update && sudo apt upgrade -y # ===== 2. 设置主机名 ===== sudo hostnamectl set-hostname cloudnest # ===== 3. 关闭 Swap(K8s 要求,后面用小 Swap 代替)===== sudo swapoff -a sudo sed -i '/swap/d' /etc/fstab # ===== 4. 创建 2GB Swap 文件(K8s 1.28+ 允许有限 Swap)===== sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab # ===== 5. 配置 K8s 内核参数 ===== cat < **预期输出**:节点状态为 `Ready`,Traefik 相关 Pod 正在运行。 ### 2.4 安装 Helm(K8s 包管理器) ```bash # ===== 下载 Helm ===== curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash # ===== 验证 ===== helm version ``` --- ## 三、部署基础设施(MySQL + Redis + MinIO) ### 3.1 创建命名空间 ```bash kubectl create namespace cloudnest ``` ### 3.2 部署 MySQL 创建文件 `mysql-deployment.yaml`: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: mysql namespace: cloudnest spec: replicas: 1 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0 env: - name: MYSQL_ROOT_PASSWORD value: "cloudnest123" - name: MYSQL_DATABASE value: "cloudnest" ports: - containerPort: 3306 resources: limits: memory: "128Mi" cpu: "500m" requests: memory: "64Mi" cpu: "100m" volumeMounts: - name: mysql-data mountPath: /var/lib/mysql volumes: - name: mysql-data hostPath: path: /data/mysql type: DirectoryOrCreate --- apiVersion: v1 kind: Service metadata: name: mysql namespace: cloudnest spec: selector: app: mysql ports: - port: 3306 targetPort: 3306 ``` ```bash kubectl apply -f mysql-deployment.yaml kubectl get pods -n cloudnest -w # 等待 MySQL 就绪 ``` ### 3.3 部署 Redis 创建文件 `redis-deployment.yaml`: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: redis namespace: cloudnest spec: replicas: 1 selector: matchLabels: app: redis template: metadata: labels: app: redis spec: containers: - name: redis image: redis:7-alpine ports: - containerPort: 6379 resources: limits: memory: "64Mi" cpu: "200m" requests: memory: "32Mi" cpu: "50m" --- apiVersion: v1 kind: Service metadata: name: redis namespace: cloudnest spec: selector: app: redis ports: - port: 6379 targetPort: 6379 ``` ```bash kubectl apply -f redis-deployment.yaml ``` ### 3.4 部署 MinIO(分布式存储核心) 创建文件 `minio-deployment.yaml`: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: minio namespace: cloudnest spec: replicas: 1 selector: matchLabels: app: minio template: metadata: labels: app: minio spec: containers: - name: minio image: minio/minio:latest args: - server - /data - --console-address - ":9001" env: - name: MINIO_ROOT_USER value: "minioadmin" - name: MINIO_ROOT_PASSWORD value: "minioadmin" ports: - containerPort: 9000 name: api - containerPort: 9001 name: console resources: limits: memory: "256Mi" cpu: "500m" requests: memory: "128Mi" cpu: "100m" volumeMounts: - name: minio-data mountPath: /data volumes: - name: minio-data hostPath: path: /data/minio type: DirectoryOrCreate --- apiVersion: v1 kind: Service metadata: name: minio namespace: cloudnest spec: selector: app: minio ports: - name: api port: 9000 targetPort: 9000 - name: console port: 9001 targetPort: 9001 --- apiVersion: networking.k8s.io/v1 kind: IngressRoute metadata: name: minio-console namespace: cloudnest spec: entryPoints: - websecure routes: - match: Host(`minio.yourdomain.com`) kind: Rule services: - name: minio port: 9001 tls: certResolver: letsencrypt ``` ```bash kubectl apply -f minio-deployment.yaml # 验证 MinIO 就绪 kubectl get pods -n cloudnest ``` > 部署完成后,通过 `https://minio.yourdomain.com` 访问 MinIO 管理控制台。 > 用户名:`minioadmin`,密码:`minioadmin` ### 3.5 验证基础设施 ```bash # 查看所有 Pod 状态 kubectl get pods -n cloudnest # 预期输出: # NAME READY STATUS RESTARTS AGE # mysql-xxxxxxxxx-xxxxx 1/1 Running 0 2m # redis-xxxxxxxxx-xxxxx 1/1 Running 0 1m # minio-xxxxxxxxx-xxxxx 1/1 Running 0 1m ``` --- ## 四、微服务开发(Golang) ### 4.1 项目结构 ``` cloudnest/ ├── cmd/ │ ├── auth-service/ │ │ └── main.go # 认证服务入口 │ └── file-service/ │ └── main.go # 文件服务入口 ├── internal/ │ ├── auth/ │ │ ├── handler.go # HTTP 处理器 │ │ ├── model.go # 数据模型 │ │ └── service.go # 业务逻辑 │ ├── file/ │ │ ├── handler.go # HTTP 处理器 │ │ ├── model.go # 数据模型 │ │ └── service.go # 业务逻辑 │ └── middleware/ │ └── jwt.go # JWT 中间件 ├── pkg/ │ ├── config/ │ │ └── config.go # 配置管理 │ └── storage/ │ └── minio.go # MinIO 客户端封装 ├── deployments/ │ ├── k8s/ │ │ ├── auth-deployment.yaml │ │ ├── file-deployment.yaml │ │ └── ingress.yaml │ └── docker/ │ ├── auth-service.Dockerfile │ └── file-service.Dockerfile ├── go.mod └── go.sum ``` ### 4.2 初始化 Go 项目 ```bash # 在本地开发机执行 mkdir cloudnest && cd cloudnest go mod init github.com/yourname/cloudnest # 安装依赖 go get github.com/gin-gonic/gin go get github.com/minio/minio-go/v7 go get github.com/golang-jwt/jwt/v5 go get github.com/go-sql-driver/mysql go get github.com/redis/go-redis/v9 go get golang.org/x/crypto/bcrypt ``` ### 4.3 配置管理 `pkg/config/config.go` ```go package config import "os" type Config struct { // Auth Service AuthPort string JWTSecret string MySQLDSN string RedisAddr string // File Service FilePort string MinioEndpoint string MinioAccessKey string MinioSecretKey string MinioBucket string MinioUseSSL bool } func Load() *Config { return &Config{ AuthPort: getEnv("AUTH_PORT", "8081"), JWTSecret: getEnv("JWT_SECRET", "cloudnest-secret-key"), MySQLDSN: getEnv("MYSQL_DSN", "root:sun900120@tcp(mysql:3306)/cloudnest?charset=utf8mb4&parseTime=True"), RedisAddr: getEnv("REDIS_ADDR", "redis:6379"), FilePort: getEnv("FILE_PORT", "8082"), MinioEndpoint: getEnv("MINIO_ENDPOINT", "minio:9000"), MinioAccessKey: getEnv("MINIO_ACCESS_KEY", "minioadmin"), MinioSecretKey: getEnv("MINIO_SECRET_KEY", "minioadmin"), MinioBucket: getEnv("MINIO_BUCKET", "cloudnest-files"), MinioUseSSL: false, } } func getEnv(key, fallback string) string { if value := os.Getenv(key); value != "" { return value } return fallback } ``` ### 4.4 Auth Service(认证服务) **`cmd/auth-service/main.go`** ```go package main import ( "github.com/gin-gonic/gin" "github.com/yourname/cloudnest/internal/auth" "github.com/yourname/cloudnest/pkg/config" ) func main() { cfg := config.Load() r := gin.Default() // 初始化数据库连接 auth.InitDB(cfg.MySQLDSN) auth.InitRedis(cfg.RedisAddr) // 路由 auth.RegisterRoutes(r, cfg.JWTSecret) r.Run(":" + cfg.AuthPort) } ``` **`internal/auth/model.go`** ```go package auth import "time" type User struct { ID uint `json:"id" gorm:"primaryKey"` Username string `json:"username" gorm:"uniqueIndex;size:50"` Password string `json:"-" gorm:"size:255"` CreatedAt time.Time `json:"created_at"` } type RegisterRequest struct { Username string `json:"username" binding:"required,min=3,max=50"` Password string `json:"password" binding:"required,min=6"` } type LoginRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } type TokenResponse struct { Token string `json:"token"` } ``` **`internal/auth/service.go`** ```go package auth import ( "context" "time" "github.com/golang-jwt/jwt/v5" "github.com/redis/go-redis/v9" "golang.org/x/crypto/bcrypt" "gorm.io/driver/mysql" "gorm.io/gorm" ) var ( db *gorm.DB rdb *redis.Client ctx = context.Background() ) func InitDB(dsn string) { var err error db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic("failed to connect database: " + err.Error()) } db.AutoMigrate(&User{}) } func InitRedis(addr string) { rdb = redis.NewClient(&redis.Options{Addr: addr}) } func Register(username, password string) error { hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return err } return db.Create(&User{Username: username, Password: string(hashed)}).Error } func Login(username, password string) (string, error) { var user User if err := db.Where("username = ?", username).First(&user).Error; err != nil { return "", err } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { return "", err } token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "user_id": user.ID, "username": user.Username, "exp": time.Now().Add(24 * time.Hour).Unix(), }) return token.SignedString([]byte("cloudnest-secret-key")) } ``` **`internal/auth/handler.go`** ```go package auth import ( "net/http" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) func RegisterRoutes(r *gin.Engine, jwtSecret string) { r.POST("/register", handleRegister) r.POST("/login", handleLogin) r.GET("/me", authMiddleware(jwtSecret), handleMe) } func handleRegister(c *gin.Context) { var req RegisterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := Register(req.Username, req.Password); err != nil { c.JSON(http.StatusConflict, gin.H{"error": "用户名已存在"}) return } c.JSON(http.StatusCreated, gin.H{"message": "注册成功"}) } func handleLogin(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } token, err := Login(req.Username, req.Password) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"}) return } c.JSON(http.StatusOK, TokenResponse{Token: token}) } func handleMe(c *gin.Context) { claims, _ := c.Get("claims") c.JSON(http.StatusOK, claims) } func authMiddleware(jwtSecret string) gin.HandlerFunc { return func(c *gin.Context) { tokenStr := c.GetHeader("Authorization") if len(tokenStr) < 7 || tokenStr[:7] != "Bearer " { c.JSON(http.StatusUnauthorized, gin.H{"error": "未提供认证令牌"}) c.Abort() return } token, err := jwt.Parse(tokenStr[7:], func(t *jwt.Token) (interface{}, error) { return []byte(jwtSecret), nil }) if err != nil || !token.Valid { c.JSON(http.StatusUnauthorized, gin.H{"error": "令牌无效"}) c.Abort() return } c.Set("claims", token.Claims) c.Next() } } ``` ### 4.5 File Service(文件服务) **`pkg/storage/minio.go`** ```go package storage import ( "context" "io" "time" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) var client *minio.Client func Init(endpoint, accessKey, secretKey string, useSSL bool) error { var err error client, err = minio.New(endpoint, &minio.Options{ Creds: credentials.NewStaticV4(accessKey, secretKey, ""), Secure: useSSL, }) return err } func EnsureBucket(bucket string) error { ctx := context.Background() exists, err := client.BucketExists(ctx, bucket) if err != nil { return err } if !exists { return client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}) } return nil } func UploadFile(bucket, objectName string, reader io.Reader, size int64, contentType string) error { _, err := client.PutObject(context.Background(), bucket, objectName, reader, size, minio.PutObjectOptions{ContentType: contentType}) return err } func GetPresignedURL(bucket, objectName string) (string, error) { url, err := client.PresignedGetObject(context.Background(), bucket, objectName, 24*3600*time.Second, nil) if err != nil { return "", err } return url.String(), nil } func ListFiles(bucket, prefix string) ([]string, error) { ctx := context.Background() var files []string objects := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{Prefix: prefix}) for obj := range objects { if obj.Err != nil { return nil, obj.Err } files = append(files, obj.Key) } return files, nil } func DeleteFile(bucket, objectName string) error { return client.RemoveObject(context.Background(), bucket, objectName) } ``` **`cmd/file-service/main.go`** ```go package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/yourname/cloudnest/pkg/config" "github.com/yourname/cloudnest/pkg/storage" ) func main() { cfg := config.Load() // 初始化 MinIO if err := storage.Init(cfg.MinioEndpoint, cfg.MinioAccessKey, cfg.MinioSecretKey, cfg.MinioUseSSL); err != nil { panic("failed to connect minio: " + err.Error()) } storage.EnsureBucket(cfg.MinioBucket) r := gin.Default() // 文件操作需要认证 fileGroup := r.Group("/", authMiddleware(cfg.JWTSecret)) { fileGroup.POST("/upload", handleUpload) fileGroup.GET("/files", handleList) fileGroup.GET("/download/:name", handleDownload) fileGroup.DELETE("/files/:name", handleDelete) } r.Run(":" + cfg.FilePort) } func authMiddleware(jwtSecret string) gin.HandlerFunc { return func(c *gin.Context) { tokenStr := c.GetHeader("Authorization") if len(tokenStr) < 7 || tokenStr[:7] != "Bearer " { c.JSON(http.StatusUnauthorized, gin.H{"error": "未提供认证令牌"}) c.Abort() return } token, _ := jwt.Parse(tokenStr[7:], func(t *jwt.Token) (interface{}, error) { return []byte(jwtSecret), nil }) if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { c.Set("username", claims["username"]) } else { c.JSON(http.StatusUnauthorized, gin.H{"error": "令牌无效"}) c.Abort() return } c.Next() } } func handleUpload(c *gin.Context) { file, header, err := c.Request.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "文件上传失败"}) return } defer file.Close() username := c.GetString("username") objectName := fmt.Sprintf("%s/%s", username, header.Filename) if err := storage.UploadFile("cloudnest-files", objectName, file, header.Size, header.Header.Get("Content-Type")); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "存储失败"}) return } c.JSON(http.StatusOK, gin.H{ "message": "上传成功", "file": objectName, "size": header.Size, }) } func handleList(c *gin.Context) { username := c.GetString("username") files, err := storage.ListFiles("cloudnest-files", username+"/") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取文件列表失败"}) return } c.JSON(http.StatusOK, gin.H{"files": files}) } func handleDownload(c *gin.Context) { username := c.GetString("username") name := c.Param("name") objectName := fmt.Sprintf("%s/%s", username, name) url, err := storage.GetPresignedURL("cloudnest-files", objectName) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"}) return } c.JSON(http.StatusOK, gin.H{"download_url": url}) } func handleDelete(c *gin.Context) { username := c.GetString("username") name := c.Param("name") objectName := fmt.Sprintf("%s/%s", username, name) if err := storage.DeleteFile("cloudnest-files", objectName); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"}) return } c.JSON(http.StatusOK, gin.H{"message": "删除成功"}) } ``` ### 4.6 Dockerfile **`deployments/docker/auth-service.Dockerfile`** ```dockerfile # 多阶段构建 - 减小镜像体积 FROM golang:1.25.1-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o auth-service ./cmd/auth-service FROM alpine:3.19 RUN apk --no-cache add ca-certificates WORKDIR /app COPY --from=builder /app/auth-service . EXPOSE 8081 CMD ["./auth-service"] ``` **`deployments/docker/file-service.Dockerfile`** ```dockerfile FROM golang:1.22-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o file-service ./cmd/file-service FROM alpine:3.19 RUN apk --no-cache add ca-certificates WORKDIR /app COPY --from=builder /app/file-service . EXPOSE 8082 CMD ["./file-service"] ``` --- ## 五、K8s 部署微服务 ### 5.1 构建并推送镜像 在本地开发机上执行(需要先安装 Docker): ```bash # 进入项目根目录 cd cloudnest # ===== 方案 A:推送到 Docker Hub(推荐)===== # 1. 登录 Docker Hub docker login # 2. 构建镜像 docker build -f deployments/docker/auth-service.Dockerfile -t yourname/auth-service:v1 . docker build -f deployments/docker/file-service.Dockerfile -t yourname/file-service:v1 . # 3. 推送镜像 docker push yourname/auth-service:v1 docker push yourname/file-service:v1 # ===== 方案 B:直接在服务器上构建(无需 Docker Hub)===== # 将代码上传到服务器后,在服务器上执行: # docker build -f deployments/docker/auth-service.Dockerfile -t auth-service:v1 . # docker build -f deployments/docker/file-service.Dockerfile -t file-service:v1 . ``` ### 5.2 部署 Auth Service 创建文件 `deployments/k8s/auth-deployment.yaml`: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: auth-service namespace: cloudnest spec: replicas: 1 selector: matchLabels: app: auth-service template: metadata: labels: app: auth-service spec: containers: - name: auth-service image: yourname/auth-service:v1 # 如果在服务器本地构建,改为: # imagePullPolicy: Never # image: auth-service:v1 ports: - containerPort: 8081 env: - name: AUTH_PORT value: "8081" - name: JWT_SECRET value: "cloudnest-secret-key" - name: MYSQL_DSN value: "root:cloudnest123@tcp(mysql:3306)/cloudnest?charset=utf8mb4&parseTime=True" - name: REDIS_ADDR value: "redis:6379" resources: limits: memory: "64Mi" cpu: "200m" requests: memory: "32Mi" cpu: "50m" --- apiVersion: v1 kind: Service metadata: name: auth-service namespace: cloudnest spec: selector: app: auth-service ports: - port: 8081 targetPort: 8081 ``` ### 5.3 部署 File Service 创建文件 `deployments/k8s/file-deployment.yaml`: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: file-service namespace: cloudnest spec: replicas: 1 selector: matchLabels: app: file-service template: metadata: labels: app: file-service spec: containers: - name: file-service image: yourname/file-service:v1 # 如果在服务器本地构建,改为: # imagePullPolicy: Never # image: file-service:v1 ports: - containerPort: 8082 env: - name: FILE_PORT value: "8082" - name: JWT_SECRET value: "cloudnest-secret-key" - name: MINIO_ENDPOINT value: "minio:9000" - name: MINIO_ACCESS_KEY value: "minioadmin" - name: MINIO_SECRET_KEY value: "minioadmin" - name: MINIO_BUCKET value: "cloudnest-files" resources: limits: memory: "64Mi" cpu: "200m" requests: memory: "32Mi" cpu: "50m" --- apiVersion: v1 kind: Service metadata: name: file-service namespace: cloudnest spec: selector: app: file-service ports: - port: 8082 targetPort: 8082 ``` ### 5.4 配置 Ingress(域名路由 + SSL) 创建文件 `deployments/k8s/ingress.yaml`: ```yaml apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: cloudnest-ingress namespace: cloudnest spec: entryPoints: - websecure routes: - match: Host(`api.yourdomain.com`) && PathPrefix(`/auth`) kind: Rule services: - name: auth-service port: 8081 - match: Host(`api.yourdomain.com`) && PathPrefix(`/files`) kind: Rule services: - name: file-service port: 8082 tls: certResolver: letsencrypt --- apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: strip-prefix namespace: cloudnest spec: stripPrefix: prefixes: - /auth - /files ``` ### 5.5 配置 Let's Encrypt 自动证书 ```bash # 创建 Traefik 配置补丁,启用 Let's Encrypt cat <" \ -F "file=@/path/to/your/test.txt" # ===== 4. 查看文件列表 ===== curl https://api.yourdomain.com/files/files \ -H "Authorization: Bearer <你的token>" # ===== 5. 下载文件 ===== curl https://api.yourdomain.com/files/download/test.txt \ -H "Authorization: Bearer <你的token>" ``` ### 7.3 MinIO 控制台 浏览器访问 `https://minio.yourdomain.com`,登录后可查看上传的文件。 --- ## 八、日常运维命令速查 ```bash # 查看日志 kubectl logs -f deployment/auth-service -n cloudnest kubectl logs -f deployment/file-service -n cloudnest # 重启服务 kubectl rollout restart deployment/auth-service -n cloudnest kubectl rollout restart deployment/file-service -n cloudnest # 查看资源占用 kubectl top pods -n cloudnest free -h # 系统内存 df -h # 磁盘使用 # 进入容器调试 kubectl exec -it deployment/auth-service -n cloudnest -- sh # 查看 Service 详情 kubectl describe svc auth-service -n cloudnest ``` --- ## 九、学习路线建议 | 阶段 | 学习目标 | 实操内容 | |------|---------|---------| | **第1周** | 熟悉 K8s 基础概念 | 部署 k3s,理解 Pod/Service/Deployment | | **第2周** | 微服务通信 | 理解 Service 发现,修改 Ingress 路由规则 | | **第3周** | 分布式存储 | 学习 MinIO 操作,理解对象存储概念 | | **第4周** | 持久化与配置 | 将 hostPath 改为 PVC,使用 ConfigMap/Secret | | **第5周** | 扩展练习 | 添加日志服务(Loki),监控(Prometheus) | ### 可扩展方向 - **添加 Horizontal Pod Autoscaler**:根据 CPU 自动扩缩容 - **引入 ConfigMap/Secret**:替代硬编码的配置和密码 - **添加日志收集**:Loki + Grafana - **添加监控**:Prometheus + Grafana - **添加 CI/CD**:GitHub Actions 自动构建推送镜像 - **数据库迁移**:hostPath → PVC 持久卷 --- ## 附录:快速部署脚本 将以下脚本保存为 `deploy-all.sh`,一键部署所有基础设施: ```bash #!/bin/bash set -e echo "=== CloudNest 一键部署脚本 ===" # 创建命名空间 kubectl create namespace cloudnest 2>/dev/null || true # 部署基础设施 echo "[1/3] 部署 MySQL..." kubectl apply -f mysql-deployment.yaml echo "[2/3] 部署 Redis..." kubectl apply -f redis-deployment.yaml echo "[3/3] 部署 MinIO..." kubectl apply -f minio-deployment.yaml echo "等待基础设施就绪..." kubectl wait --for=condition=ready pod -l app=mysql -n cloudnest --timeout=120s kubectl wait --for=condition=ready pod -l app=redis -n cloudnest --timeout=60s kubectl wait --for=condition=ready pod -l app=minio -n cloudnest --timeout=60s echo "=== 基础设施部署完成 ===" echo "MySQL: mysql.cloudnest.svc.cluster.local:3306" echo "Redis: redis.cloudnest.svc.cluster.local:6379" echo "MinIO: minio.cloudnest.svc.cluster.local:9000" echo "" echo "下一步:构建微服务镜像并部署" echo " kubectl apply -f deployments/k8s/auth-deployment.yaml" echo " kubectl apply -f deployments/k8s/file-deployment.yaml" echo " kubectl apply -f deployments/k8s/ingress.yaml" ``` ```bash chmod +x deploy-all.sh ./deploy-all.sh ``` --- > **提示**:本文档中 `yourdomain.com`、`yourname`、`your-email@example.com` 等占位符请替换为你的实际值。 > 所有密码仅用于学习环境,生产环境请使用 Secret 管理敏感信息。