Kubernetes 生产级实战:从零部署 LNMP 架构 ECShop 商城(完整指南)

手把手教你用 K8s 部署一个高可用的 LNMP 电商网站,涵盖存储、配额、健康检查、HPA、Ingress、Dashboard 全链路


前言

ECShop 是一款经典的 PHP 开源电商系统,LNMP(Linux + Nginx + MySQL + PHP)是 Web 应用最经典的架构之一。本文将带你 在 Kubernetes 集群中完整部署一套 ECShop 商城,不仅实现基础功能,还融入了生产环境必备的:

  • 动态存储卷供应(NFS)
  • 资源配额与限制(ResourceQuota + LimitRange)
  • 有状态应用管理(MySQL StatefulSet)
  • 健康探针(Liveness / Readiness)
  • 自动扩缩容(HPA)
  • 安全通信(Ingress + TLS)
  • 图形化管理(Kubernetes Dashboard)

阅读本文前,你需要具备 Kubernetes 基础操作能力,了解 YAML 编写和 kubectl 使用。所有示例基于 K8s v1.30 + containerd + Calico。


整体架构

我们使用以下架构图展示各组件之间的关系:

Dashboard_GROUP

Nginx_GROUP

PHP_GROUP

MySQL_GROUP

right

NFS服务器
10.1.8.30 /ecshop

PV 动态卷制备

PVC-mysql

ConfigMap: nginx-config

PVC-ecshop

Secret: mysql账号密码

Secret: nginx-tls
通用TLS证书

Statefulset: mysql

Service: mysql HeadLess

Service: mysql ClusterIP

Deployment: php

Service: php ClusterIP

Deployment: nginx

Service: nginx ClusterIP

Deployment: kubernetes-dashboard

Service: kubernetes-dashboard ClusterIP

MetalLB 负载均衡
分配外部LB VIP

Ingress-Nginx Controller
集群入口网关

Ingress规则: shop.laoma.cloud

Ingress规则: dashboard.laoma.cloud

  • 存储层:NFS 提供共享目录,动态卷制备(StorageClass)为 MySQL 和 ECShop 代码提供持久存储。
  • 应用层:MySQL(StatefulSet)、PHP(Deployment)、Nginx(Deployment)各自独立,通过 Service 通信。
  • 网关层:MetalLB 分配外部 IP,Ingress-Nginx 作为七层路由,TLS 证书保障 HTTPS。
  • 可观测性:Metrics Server + HPA 实现自动扩缩容,Dashboard 提供 Web 管理界面。

一、环境准备

1.1 Kubernetes 集群

  • 版本:v1.30
  • 节点:1 Master + 2 Worker(IP:10.1.8.30/31/32)
  • 网络插件:Calico
  • 容器运行时:containerd

1.2 部署 NFS 服务器(在 Master 或独立节点)

# 安装 NFS 服务端
apt install -y nfs-kernel-server

# 创建共享目录并设置权限
mkdir -m 777 /ecshop

# 编辑 /etc/exports
echo "/ecshop *(rw,sync,no_root_squash,no_all_squash,insecure)" >> /etc/exports

# 重启服务
systemctl restart nfs-server

Worker 节点安装 NFS 客户端:

apt install -y nfs-common

二、存储类与命名空间资源配额

2.1 创建 NFS 动态卷制备器

使用 nfs-subdir-external-provisioner 实现动态供应。部署文件(nfs-provisioner.yaml):

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
  name: nfs-client-provisioner
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  namespace: kube-system
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      volumes:
      - name: nfs-client-root
        nfs:
          server: 10.1.8.30
          path: /ecshop
      containers:
      - name: nfs-client-provisioner
        image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
        volumeMounts:
        - name: nfs-client-root
          mountPath: /persistentvolumes
        env:
        - name: PROVISIONER_NAME
          value: fuseim.pri/ifs
        - name: NFS_SERVER
          value: 10.1.8.30
        - name: NFS_PATH
          value: /ecshop

创建 StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ecshop-storage
provisioner: fuseim.pri/ifs
reclaimPolicy: Delete
volumeBindingMode: Immediate

应用:

kubectl apply -f nfs-provisioner.yaml
kubectl apply -f storageclass.yaml

2.2 创建命名空间与资源配额

kubectl create ns ecshop

ResourceQuotaquota.yaml):

apiVersion: v1
kind: ResourceQuota
metadata:
  name: ecshop-quota
  namespace: ecshop
spec:
  hard:
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.cpu: "4"
    limits.memory: 8Gi
    requests.storage: 500Gi

LimitRangelimitrange.yaml):

apiVersion: v1
kind: LimitRange
metadata:
  name: ecshop-limits
  namespace: ecshop
spec:
  limits:
  - type: Container
    max:
      cpu: "1"
      memory: 2Gi
    min:
      cpu: 100m
      memory: 256Mi
    default:
      cpu: 500m
      memory: 512Mi
    defaultRequest:
      cpu: 200m
      memory: 256Mi

应用:

kubectl apply -f quota.yaml -f limitrange.yaml

三、部署 MySQL(StatefulSet)

3.1 创建 Secret 存储数据库凭证

apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
  namespace: ecshop
type: Opaque
stringData:
  root-password: "Laoma@123"
  ecshop-user: "ecshop"
  ecshop-password: "Laoma@123"
  ecshop-database: "ecshop"

3.2 Headless Service 与 ClusterIP Service

Headless Service(用于 StatefulSet 网络标识):

apiVersion: v1
kind: Service
metadata:
  name: mysql-headless
  namespace: ecshop
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
  - port: 3306
    targetPort: 3306

ClusterIP Service(供 PHP 应用访问):

apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: ecshop
spec:
  selector:
    app: mysql
  ports:
  - port: 3306
    targetPort: 3306

3.3 StatefulSet 配置

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: ecshop
spec:
  serviceName: mysql-headless
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: root-password
        - name: MYSQL_DATABASE
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: ecshop-database
        - name: MYSQL_USER
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: ecshop-user
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: ecshop-password
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
        livenessProbe:
          exec:
            command: ["mysqladmin", "ping", "-h", "localhost", "-p${MYSQL_ROOT_PASSWORD}"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command: ["mysql", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}", "-e", "SELECT 1"]
          initialDelaySeconds: 10
          periodSeconds: 5
        resources:
          requests:
            cpu: 200m
            memory: 512Mi
          limits:
            cpu: 500m
            memory: 1Gi
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ecshop-storage
      resources:
        requests:
          storage: 50Gi

四、部署 PHP(Deployment)

4.1 镜像准备

需要构建一个包含 ECShop 代码的 PHP-FPM 镜像。本文使用预构建镜像 php:7.2-fpm-ecshop(基于官方 php:7.2-fpm,复制 ECShop 源码到 /var/www/html)。

4.2 Deployment 与 Service

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php
  namespace: ecshop
spec:
  replicas: 1
  selector:
    matchLabels:
      app: php
  template:
    metadata:
      labels:
        app: php
    spec:
      containers:
      - name: php-fpm
        image: php:7.2-fpm-ecshop
        ports:
        - containerPort: 9000
        volumeMounts:
        - name: ecshop-code
          mountPath: /var/www/html
        livenessProbe:
          tcpSocket:
            port: 9000
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          exec:
            command: ["php", "-v"]   # 简单验证 PHP 可执行
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            cpu: 200m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi
      volumes:
      - name: ecshop-code
        persistentVolumeClaim:
          claimName: ecshop-code-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: php
  namespace: ecshop
spec:
  selector:
    app: php
  ports:
  - port: 9000
    targetPort: 9000

PVC 用于存放 ECShop 代码(由 NFS 动态供应):

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ecshop-code-pvc
  namespace: ecshop
spec:
  accessModes: ["ReadWriteMany"]
  storageClassName: ecshop-storage
  resources:
    requests:
      storage: 10Gi

注意:PHP 和 Nginx 需要共享代码,因此使用 ReadWriteMany


五、部署 Nginx(Deployment)

5.1 ConfigMap 存储 Nginx 配置

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
  namespace: ecshop
data:
  default.conf: |
    server {
        listen 80;
        server_name shop.laoma.cloud;
        root /var/www/html;
        index index.php index.html;

        location / {
            try_files $uri $uri/ /index.php?$args;
        }

        location ~ \.php$ {
            fastcgi_pass php:9000;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }

5.2 Deployment 与 Service

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: ecshop
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.24
        ports:
        - containerPort: 80
        volumeMounts:
        - name: ecshop-code
          mountPath: /var/www/html
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: default.conf
        livenessProbe:
          httpGet:
            path: /index.php
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /index.php
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 200m
            memory: 256Mi
      volumes:
      - name: ecshop-code
        persistentVolumeClaim:
          claimName: ecshop-code-pvc
      - name: nginx-config
        configMap:
          name: nginx-config
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: ecshop
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80

六、Ingress 与 TLS(外部访问)

6.1 部署 MetalLB(裸机 LB)

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml

配置 IP 地址池(ippool.yaml):

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: ecshop-pool
  namespace: metallb-system
spec:
  addresses:
  - 10.1.8.40-10.1.8.50
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: ecshop-l2
  namespace: metallb-system

6.2 部署 Ingress-Nginx Controller

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml

(若国内网络慢,请替换镜像源)

6.3 创建 TLS 证书 Secret

# 生成自签名证书(生产环境应使用正式证书)
openssl req -x509 -newkey rsa:2048 -nodes -keyout tls.key -out tls.crt -days 365 -subj "/CN=*.laoma.cloud"

kubectl create secret tls nginx-tls --key=tls.key --cert=tls.crt -n ecshop

6.4 创建 Ingress 规则

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ecshop-ingress
  namespace: ecshop
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - shop.laoma.cloud
    secretName: nginx-tls
  rules:
  - host: shop.laoma.cloud
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 80

七、HPA 自动扩缩容

7.1 部署 Metrics Server

(参考之前文章,需添加 --kubelet-insecure-tls

7.2 创建 HPA 规则

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-hpa
  namespace: ecshop
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php
  minReplicas: 2
  maxReplicas: 5
  metrics:
  - resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60
    type: Resource
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-hpa
  namespace: ecshop
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx
  minReplicas: 2
  maxReplicas: 5
  metrics:
  - resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60
    type: Resource

八、部署 Kubernetes Dashboard 并配置 Ingress

8.1 部署 Dashboard

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.6.1/aio/deploy/recommended.yaml

8.2 创建管理员 ServiceAccount 并获取 Token

apiVersion: v1
kind: ServiceAccount
metadata:
  name: dashboard-admin
  namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dashboard-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: dashboard-admin
  namespace: kubernetes-dashboard
kubectl -n kubernetes-dashboard create token dashboard-admin

8.3 创建 Dashboard Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: dashboard-ingress
  namespace: kubernetes-dashboard
  annotations:
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - dashboard.laoma.cloud
    secretName: nginx-tls   # 复用同一个证书
  rules:
  - host: dashboard.laoma.cloud
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: kubernetes-dashboard
            port:
              number: 443

九、验证与测试

9.1 检查资源状态

kubectl get all -n ecshop
kubectl get pvc -n ecshop
kubectl get hpa -n ecshop
kubectl get ingress -n ecshop

9.2 访问站点

配置本地 hosts(或 DNS),将 shop.laoma.clouddashboard.laoma.cloud 解析到 MetalLB 分配的 Ingress IP(例如 10.1.8.40)。

浏览器打开 https://shop.laoma.cloud,按照 ECShop 安装向导完成数据库连接配置(使用 Service mysql,端口 3306,用户名 ecshop,密码 Laoma@123,数据库 ecshop)。

安装成功后,商城即可运行。

9.3 测试 HPA

增加负载观察扩容:

# 压测 PHP 服务(假设 Service ClusterIP)
ab -n 100000 -c 100 http://<php-svc-ip>:9000/

观察 HPA 状态:

watch 'kubectl get hpa -n ecshop'

9.4 访问 Dashboard

浏览器打开 https://dashboard.laoma.cloud,使用上述 Token 登录。


十、总结与最佳实践

组件关键技术点
存储NFS 动态供应 + StorageClass,MySQL 使用 ReadWriteOnce,代码使用 ReadWriteMany
配额ResourceQuota 限制总资源,LimitRange 为 Pod 设置默认 requests/limits
数据库StatefulSet 保障持久化身份,Secret 管理敏感信息,健康探针确保可用性
应用PHP 与 Nginx 分离,共享代码 PVC,Nginx 配置通过 ConfigMap 管理
网关MetalLB + Ingress-Nginx,TLS 证书统一管理,HTTPS 强制跳转
扩缩容Metrics Server + HPA 按 CPU 自动调整副本数,从容应对流量波动
运维Dashboard 提供 Web 管理,方便查看集群状态和资源

常见问题排查

  • MySQL 启动失败:检查 NFS 权限和存储类是否正常;确保 Secret 中的密码正确。
  • PHP 无法连接 MySQL:确认 Service 名称 mysql 解析正确,检查数据库是否就绪。
  • Ingress 返回 404:检查 Ingress 规则路径和后端 Service 端口匹配。
  • HPA 不生效:确认 Pod 设置了 resources 且 Metrics Server 工作正常。

结语

至此,我们成功在 Kubernetes 中部署了一套完整的 LNMP 架构 ECShop 商城,并集成了存储卷、资源配额、健康检查、自动扩缩容、安全网关和图形化运维等生产级特性。这套架构模式可以复用于大多数 PHP/Java Web 应用,希望对你的云原生实战有所帮助。

如果觉得内容实用,欢迎 点赞、收藏、转发,也欢迎在评论区交流你的部署经验!


所有 YAML 配置文件已尽量精简,可根据实际环境调整镜像版本和资源大小。如需完整代码包,可在评论区留言。

更多推荐