从 Docker Compose 到集群部署:K8s 核心概念与迁移教程

当你从单机上的 Docker Compose 转向服务器集群(如 Kubernetes)时,应用的管理方式会发生根本性的变化。集群提供了高可用性、弹性和自动扩展能力,但也引入了新的抽象概念。

第一章:核心概念解析

在集群环境中,我们不再直接管理“容器”,而是管理“负载”(Workload)和“服务”(Service)。以下是你提到的几个关键概念:

1. 无状态负载 (Stateless Workload)

  • 对应 Kubernetes 对象:Deployment

  • 核心理念: 这种负载中的所有实例(Pod)都是完全相同的、可互换的。它们不保存任何持久化数据。如果一个实例崩溃了,集群可以毫秒级地创建另一个新实例来替换它,而不会丢失任何状态。

  • 例子: Web 服务器(Nginx, Apache)、API 后端、微服务网关。

  • 类比: 像是一家快餐店的收银员。任何一个收银员都可以为你服务,他们不需要“记住”你昨天的订单。

2. 有状态负载 (StatefulSet)

  • 对应 Kubernetes 对象:StatefulSet

  • 核心理念: 这是为你提供的 Neo4j(数据库)准备的。这种负载中的每个实例(Pod)都有一个稳定且唯一的标识(例如 neo4j-0, neo4j-1),并且通常绑定了自己专属的持久化存储

  • 关键特性:

    • 稳定的网络 ID: 即使 Pod 重启,它的主机名和 DNS 记录也不会改变。

    • 稳定的持久存储: 每个 Pod 都有自己独立的存储卷(PVC),当 Pod 被重新调度时,它会重新挂载到完全相同的那个存储卷。

  • 例子: 数据库(MySQL, PostgreSQL, Neo4j)、消息队列(Kafka, RabbitMQ)、分布式文件系统。

  • 类比: 像是银行的保险箱。每个保险箱都有唯一的编号(身份),并且里面存放着独一无二的物品(数据)。你不能随意替换它们。

3. 存储卷 (PersistentVolume - PV)

  • 核心理念: 这是集群中实际的存储资源。它由集群管理员(运维)提前准备好。

  • 来源: 它可以是云服务商的磁盘、NFS、或像你示例中(nas.csi.volcengine.com)一样的 NAS(网络附加存储)。

  • 类比: 像是仓库里一个已经划分好、贴上标签的货架。它有固定的大小和类型。

4. 存储声明 (PersistentVolumeClaim - PVC)

  • 核心理念: 这是应用程序对存储的“请求”。它由开发者(你)在部署应用时创建。

  • 工作方式:

    • 静态供应(本教程采用): 你创建一个 PV(货架),再创建一个 PVC(提货单)来精确地“声明”要那个特定的 PV。

    • 动态供应: 你只创建 PVC(提货单),K8s 会通过 StorageClass(存储类)自动为你创建并绑定一个 PV(货架)。

  • 好处: 将应用(“我需要存储”)与基础设施(“存储具体在哪里”)解耦了。

  • 类比: 像是一张提货单

5. 服务 (Service)

  • 核心理念: 在集群中,Pod(运行容器的实例)的 IP 地址是会变化的(比如重启后)。Service(服务)的作用是提供一个稳定、不变的入口点(固定的 IP 地址和 DNS 名称),用于访问一组功能相同的 Pod。

  • 功能:

    • 服务发现: 其他应用可以通过服务名(例如 neo4j-service)来访问 Neo4j,而不需要知道具体 Pod 的 IP。

    • 负载均衡: 如果你有多个 Neo4j 实例(高可用),Service 会自动将请求分发给它们。

    • 端口映射: 类似于 Docker Compose 中的 ports,它定义了服务监听的端口(如 7474)和 Pod 容器的端口(如 7474)。

  • 类比: 像是一个团队的总机号码

6. 路由 (Ingress)

  • 核心理念: Service 主要处理集群 内部 的网络通信。而 Ingress(入口/路由)是专门为 HTTP/HTTPS 流量设计的,用于将集群外部的请求路由到集群内部的 Service

  • 功能:

    • 基于域名的路由: 例如,将 http://neo4j.your-domain.com 路由到你的 neo4j-service

    • SSL/TLS 终止: 集中处理 HTTPS 加密。

  • 类比: 像是一栋办公楼的前台/接待处

7. 保密字典 (Secret)

  • 对应 Kubernetes 对象:Secret

  • 核心理念: Secret(保密字典)是专门用于存储小块敏感数据(如密码、API 密钥、Token)的对象。

  • 工作方式: 你先创建一个 Secret 对象来安全地保存你的数据。然后,在创建 Pod(如 StatefulSet)时,你可以引用这个 Secret,将其中的数据作为环境变量或文件挂载到容器中。

  • 好处: 实现了配置与安全的解耦。你的应用配置(StatefulSet.yml)中不再包含明文密码,变得更安全,也更容易管理和分享。

  • 类比: 像是一个带锁的保险箱。你先把密码(neo4j/indemind@@)锁在保险箱(Secret)里,然后只把保险箱的钥匙(secretKeyRef)交给你的应用程序(StatefulSet)。

第二章:Docker Compose vs Kubernetes 迁移对比

下面我们将你提供的 docker-compose.yml 文件,拆解并翻译成 Kubernetes 所需的一系列 YAML 配置文件。

你的 Docker Compose 文件

version: '3.8'

services:
  neo4j:
    image: neo4j:5.20.0
    container_name: neo4j_db
    ports:
      - "7474:7474"
      - "7687:7687"
    environment:
      - NEO4J_AUTH=neo4j/indemind@@ 
    volumes:
      - /home/indemind/zy_/neo4j/docker_data:/data
    healthcheck:
      test: ["CMD", "curl", -f, "http://localhost:7474"]
      interval: 10s
      timeout: 10s
      retries: 5

迁移到 Kubernetes (K8s) 的配置(静态供应方案)

总览:

  1. PersistentVolume (PV) - 存储卷: (手动创建)定义一块指向你 NAS 的存储。

  2. PersistentVolumeClaim (PVC) - 存储声明: (手动创建)申请使用上面那块 PV。

  3. Secret (保密字典): (手动创建)用于安全地存储 Neo4j 密码。

  4. StatefulSet (有状态负载): (部署应用)管理 Neo4j,并使用 PVC 和 Secret。

  5. Service (服务): 暴露 Neo4j 的端口。

  6. Ingress (路由): (可选) 暴露 Web 界面到集群外。

1. (对应存储) -> PersistentVolume (PV)

首先,我们根据你提供的信息,创建一个 PersistentVolume (PV) 文件。

配置说明:

  • 我们不能使用你 models PV 的 /enas-cnbja0e0eda9ce19fe/model 路径,因为那个是共享的。

  • 必须首先在你的 NAS 上创建一个新目录,例如 /enas-cnbja0e0eda9ce19fe/neo4j-data

  • accessModes 对于数据库,我们使用 ReadWriteOnce (RWO),这意味着这个存储卷一次只能被一个 Pod 挂载为读写。这能有效防止数据被多个实例同时写入而损坏。

  • volumeHandle 这是一个唯一的名称,我们命名为 neo4j-pv

你可以把以下内容保存为 k8s-01-persistent-volume.yml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: neo4j-pv # PV 的唯一名称
spec:
  capacity:
    storage: 10Gi # 必须与 PVC 的申请大小一致或更大
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce # 数据库必须用 RWO
  persistentVolumeReclaimPolicy: Retain # 数据保留策略
  csi:
    driver: nas.csi.volcengine.com # 使用你指定的 CSI 驱动
    volumeHandle: neo4j-pv # 卷的唯一句柄
    volumeAttributes:
      # !! 以下信息基于你的 NAS 配置 !!
      server: "cnbja0e0eda9ce19fe.vpc-1a1zcgj9sn8cg8nveplg3708b.nas.ivolces.com"
      # !! 假设你已经在 NAS 上创建了 'neo4j-data' 目录 !!
      path: "/enas-cnbja0e0eda9ce19fe/neo4j-data" 
      fsType: "Extreme" # 基于你提供的示例
  mountOptions:
    - nolock,proto=tcp,noresvport
    - vers=3

2. (对应存储) -> PersistentVolumeClaim (PVC)

接下来,我们创建一个 PersistentVolumeClaim (PVC) 来“绑定”上面那个 PV。

配置说明:

  • storageClassName: "":设置为空字符串,是告诉 K8s 不要使用任何 StorageClass(动态供应)

  • volumeName 我们可以直接在 spec 中指定 volumeName: neo4j-pv,这样 PVC 就会绑定那个 PV。

你可以把以下内容保存为 k8s-02-persistent-volume-claim.yml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: neo4j-pvc # PVC 的名称,StatefulSet 将引用这个名称
spec:
  accessModes:
    - ReadWriteOnce # 必须与 PV 的 accessModes 匹配
  resources:
    requests:
      storage: 10Gi # 申请的存储大小,必须小于等于 PV 的容量
  
  # !! 关键:指定我们要绑定的 PV !!
  volumeName: neo4j-pv
  
  # !! 关键:禁用动态供应 !!
  storageClassName: ""

3. (对应 environment) -> Secret (保密字典)

现在,我们创建 Secret 来存储你的 NEO4J_AUTH 密码。

配置说明:

  • stringData:允许你以便于阅读的明文字符串形式提供机密数据。K8s 在存储时会自动对其进行 Base64 编码。

  • NEO4J_AUTH:这是 Secret 内部的一个“键”(Key),StatefulSet 将通过这个键来引用对应的“值”。

你可以把以下内容保存为 k8s-03-secret.yml

apiVersion: v1
kind: Secret
metadata:
  name: neo4j-secret # Secret 的名称,StatefulSet 将引用这个名称
type: Opaque # 默认的 Secret 类型
stringData:
  # 将你的密码存储在这里
  NEO4J_AUTH: "neo4j/indemind@@"

4. (对应 services: neo4j, volumes) -> StatefulSet

现在我们创建 StatefulSet,并让它使用我们准备好的 neo4j-pvcneo4j-secret

配置说明:

  • replicas: 1 非常重要! 因为我们是手动创建的一个 RWO 存储卷,这个存储卷只能被一个 Pod 读写。

  • volumes 引用我们创建的 neo4j-pvc

  • env (环境变量): 不再使用 value 明文写入密码,而是使用 valueFromsecretKeyRef 来从 neo4j-secret 中安全地引用密码。

  • enableServiceLinks: false 解决 SERVICE.PORT.7687.TCP.PROTO 错误的关键配置。

你可以把以下内容保存为 k8s-04-statefulset.yml

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: neo4j
spec:
  # serviceName 必须匹配下面 Service 的 metadata.name
  serviceName: "neo4j-service"
  
  # !! 警告:使用静态 PV/PVC 时,副本数必须为 1 !!
  replicas: 1 
  
  selector:
    matchLabels:
      app: neo4j
  template:
    metadata:
      labels:
        app: neo4j
    spec:
      # --- 解决 SERVICE.PORT 错误的关键 ---
      # 禁止 K8s 自动注入服务环境变量,避免 Neo4j 误读
      enableServiceLinks: false 
    
      # --- 卷定义:使用已存在的 PVC ---
      volumes:
      - name: data # 对应下面的 volumeMounts.name
        persistentVolumeClaim:
          # 引用我们手动创建的 PVC 的名称
          claimName: neo4j-pvc 
      
      containers:
      - name: neo4j
        # <-- 镜像源已更新 -->
        image: [swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/neo4j:5.22.0](https://swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/neo4j:5.22.0)
        ports:
        - containerPort: 7474
          name: http
        - containerPort: 7687
          name: bolt
        
        # (对应 environment) --- 从 Secret 引用密码 ---
        env:
          - name: NEO4J_AUTH
            valueFrom:
              secretKeyRef:
                name: neo4j-secret  # 引用 k8s-03-secret.yml 中创建的 Secret
                key: NEO4J_AUTH      # 引用 Secret 中的 'key'
        
        # (可选) 添加资源请求和限制
        resources:
          limits:
            cpu: "2"
            memory: 4Gi
          requests:
            cpu: "1"
            memory: 2Gi

        # (对应 healthcheck) K8s 的健康检查
        readinessProbe:
          httpGet:
            path: /
            port: 7474
          initialDelaySeconds: 20
          periodSeconds: 10
          timeoutSeconds: 10
          failureThreshold: 5
        livenessProbe:
          httpGet:
            path: /
            port: 7474
          initialDelaySeconds: 60
          periodSeconds: 10
          timeoutSeconds: 10
          failureThreshold: 5
        
        # (对应 volumes) Pod 内的挂载点
        volumeMounts:
        - name: data # 必须匹配 volumes.name
          mountPath: /data

  # --- 不再需要 volumeClaimTemplates ---
  # volumeClaimTemplates: ...

5. (对应 ports) -> Service

这个 Service 会为你的 StatefulSet(所有带 app: neo4j 标签的 Pod)创建一个集群内部的统一入口。

你可以把以下内容保存为 k8s-05-service.yml

apiVersion: v1
kind: Service
metadata:
  name: neo4j-service # 必须匹配 StatefulSet 的 serviceName
spec:
  type: ClusterIP 
  selector:
    app: neo4j # 选择所有带 'app: neo4j' 标签的 Pod
  ports:
  - port: 7474
    targetPort: 7474
    name: http
  - port: 7687
    targetPort: 7687
    name: bolt

6. (对应 “路由”) -> Ingress (可选)

如果你想通过一个域名(例如 neo4j.my-cluster.com)从外部浏览器访问 Neo4j 的 Web 界面(7474 端口),你可以创建一个 Ingress 资源。

你可以把以下内容保存为 k8s-06-ingress.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: neo4j-ingress
  # annotations:
  #   nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: "neo4j.your-domain.com" # !! 修改为你自己的域名
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: neo4j-service
            port:
              number: 7474

更多推荐