Skip to main content

使用 Minikube 实现 PHP+Vue 项目持续化发布方案

一、整体架构设计

1.1 技术栈组成

  • 前端:Vue.js 应用
  • 后端:PHP 服务(如 Laravel/Symfony)
  • 数据库:MySQL/PostgreSQL
  • 缓存:Redis
  • CI/CD:Jenkins/GitLab CI
  • 容器编排:Minikube (Kubernetes)
  • 监控:Prometheus + Grafana

1.2 部署流程

代码提交 → CI构建 → 容器镜像打包 → 推送镜像仓库 → CD部署 → K8S滚动更新 → 健康检查 → 监控反馈

二、环境准备

2.1 启动 Minikube 集群

# 启动带Ingress的Minikube集群
minikube start --driver=docker --cpus=4 --memory=8g --disk-size=50g \
  --addons=ingress,metrics-server

# 启用必要插件
minikube addons enable dashboard
minikube addons enable registry

2.2 准备命名空间

kubectl create namespace php-vue-prod
kubectl create namespace monitoring

三、项目容器化

3.1 Vue 前端 Dockerfile

# 构建阶段
FROM node:16 as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# 生产阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

3.2 PHP 后端 Dockerfile (Laravel示例)

FROM php:8.1-fpm

# 安装依赖
RUN apt-get update && apt-get install -y \
    libzip-dev \
    unzip \
    && docker-php-ext-install zip pdo_mysql

# 安装Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www
COPY . .

# 安装PHP依赖
RUN composer install --no-dev --optimize-autoloader

# 设置权限
RUN chown -R www-data:www-data /var/www/storage

EXPOSE 9000
CMD ["php-fpm"]

3.3 Nginx 配置 (前端+PHP后端)

server {
    listen 80;
    server_name localhost;
    
    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

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

    location ~ \.php$ {
        fastcgi_pass php-backend:9000;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /var/www/public$fastcgi_script_name;
    }
}

四、Kubernetes 部署文件

4.1 前端部署 (frontend-deployment.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vue-frontend
  namespace: php-vue-prod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: vue-frontend
  template:
    metadata:
      labels:
        app: vue-frontend
    spec:
      containers:
      - name: vue-app
        image: your-registry/vue-app:${IMAGE_TAG}
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: vue-frontend
  namespace: php-vue-prod
spec:
  selector:
    app: vue-frontend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

4.2 PHP后端部署 (backend-deployment.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-backend
  namespace: php-vue-prod
spec:
  replicas: 3
  selector:
    matchLabels:
      app: php-backend
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: php-backend
    spec:
      containers:
      - name: php-app
        image: your-registry/php-app:${IMAGE_TAG}
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9000
        envFrom:
        - configMapRef:
            name: php-config
        - secretRef:
            name: php-secrets
        volumeMounts:
        - name: storage
          mountPath: /var/www/storage
        resources:
          requests:
            cpu: "200m"
            memory: "256Mi"
          limits:
            cpu: "1000m"
            memory: "1Gi"
        livenessProbe:
          exec:
            command:
            - sh
            - -c
            - "php /var/www/artisan health:check"
          initialDelaySeconds: 60
          periodSeconds: 30
      volumes:
      - name: storage
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: php-backend
  namespace: php-vue-prod
spec:
  selector:
    app: php-backend
  ports:
    - protocol: TCP
      port: 9000
      targetPort: 9000

4.3 MySQL数据库部署 (mysql-statefulset.yaml)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: php-vue-prod
spec:
  serviceName: mysql
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        envFrom:
        - secretRef:
            name: mysql-secrets
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
        resources:
          requests:
            cpu: "500m"
            memory: "1Gi"
          limits:
            cpu: "2000m"
            memory: "2Gi"
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 10Gi

4.4 Ingress路由配置 (ingress.yaml)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: php-vue-ingress
  namespace: php-vue-prod
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
  - host: php-vue.local
    http:
      paths:
      - path: /(.*)
        pathType: Prefix
        backend:
          service:
            name: vue-frontend
            port:
              number: 80
      - path: /api/(.*)
        pathType: Prefix
        backend:
          service:
            name: php-backend
            port:
              number: 9000

五、CI/CD 流水线实现

5.1 Jenkinsfile 示例

pipeline {
    agent {
        kubernetes {
            yaml """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: node
    image: node:16
    command: ["cat"]
    tty: true
  - name: php
    image: composer:2
    command: ["cat"]
    tty: true
  - name: docker
    image: docker:20.10
    command: ["cat"]
    tty: true
    volumeMounts:
      - name: docker-sock
        mountPath: /var/run/docker.sock
  - name: kubectl
    image: bitnami/kubectl:latest
    command: ["cat"]
    tty: true
  volumes:
    - name: docker-sock
      hostPath:
        path: /var/run/docker.sock
"""
        }
    }
    
    environment {
        DOCKER_REGISTRY = "your-registry"
        FRONTEND_IMAGE = "${DOCKER_REGISTRY}/vue-app"
        BACKEND_IMAGE = "${DOCKER_REGISTRY}/php-app"
        K8S_NAMESPACE = "php-vue-prod"
        // 从Jenkins凭据获取
        DOCKER_CREDS = credentials('docker-hub-creds') 
        KUBE_CONFIG = credentials('kube-config')
    }
    
    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://your-repo.git'
            }
        }
        
        stage('Build Frontend') {
            steps {
                container('node') {
                    sh 'npm install'
                    sh 'npm run build'
                }
            }
        }
        
        stage('Build Backend') {
            steps {
                container('php') {
                    sh 'composer install --no-dev --optimize-autoloader'
                }
            }
        }
        
        stage('Build Docker Images') {
            steps {
                container('docker') {
                    script {
                        // 构建前端镜像
                        sh "docker build -t ${FRONTEND_IMAGE}:${GIT_COMMIT} -f Dockerfile.frontend ."
                        
                        // 构建后端镜像
                        sh "docker build -t ${BACKEND_IMAGE}:${GIT_COMMIT} -f Dockerfile.backend ."
                        
                        // 登录镜像仓库
                        sh "echo ${DOCKER_CREDS_PSW} | docker login -u ${DOCKER_CREDS_USR} --password-stdin ${DOCKER_REGISTRY}"
                        
                        // 推送镜像
                        sh "docker push ${FRONTEND_IMAGE}:${GIT_COMMIT}"
                        sh "docker push ${BACKEND_IMAGE}:${GIT_COMMIT}"
                    }
                }
            }
        }
        
        stage('Deploy to Minikube') {
            steps {
                container('kubectl') {
                    script {
                        // 配置kubectl
                        sh "mkdir -p ~/.kube"
                        writeFile file: '~/.kube/config', text: "${KUBE_CONFIG}"
                        
                        // 更新部署文件中的镜像标签
                        sh "sed -i 's|\\${IMAGE_TAG}|${GIT_COMMIT}|g' k8s/frontend-deployment.yaml"
                        sh "sed -i 's|\\${IMAGE_TAG}|${GIT_COMMIT}|g' k8s/backend-deployment.yaml"
                        
                        // 应用K8S配置
                        sh "kubectl apply -f k8s/ -n ${K8S_NAMESPACE}"
                        
                        // 等待部署完成
                        sh "kubectl rollout status deployment/vue-frontend -n ${K8S_NAMESPACE}"
                        sh "kubectl rollout status deployment/php-backend -n ${K8S_NAMESPACE}"
                    }
                }
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        failure {
            slackSend channel: '#ci-alerts', message: "Build Failed: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
        }
        success {
            slackSend channel: '#ci-alerts', message: "Build Succeeded: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
        }
    }
}

六、高级配置

6.1 数据库迁移处理

# 在backend-deployment.yaml中添加initContainer
initContainers:
- name: migrate
  image: your-registry/php-app:${IMAGE_TAG}
  command: ["sh", "-c", "php artisan migrate --force"]
  envFrom:
  - configMapRef:
      name: php-config
  - secretRef:
      name: php-secrets

6.2 配置热更新

# 使用ConfigMap存储配置
kubectl create configmap php-config --from-env-file=.env -n php-vue-prod

# 添加ConfigMap自动更新注解
annotations:
  configmap.reloader.stakater.com/reload: "php-config"

6.3 监控配置

# 添加Prometheus监控注解
metadata:
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "80"

七、本地开发与调试

7.1 使用 Skaffold 实现本地开发循环

# skaffold.yaml
apiVersion: skaffold/v2beta16
kind: Config
profiles:
- name: dev
  activation:
  - kubeContext: minikube
  deploy:
    kubectl:
      manifests:
        enabled: false
  portForward:
  - resourceType: service
    resourceName: vue-frontend
    port: 80
    localPort: 8080
  - resourceType: service
    resourceName: php-backend
    port: 9000
    localPort: 9000

7.2 访问应用

# 获取Ingress地址
minikube tunnel

# 添加hosts记录
echo "$(minikube ip) php-vue.local" | sudo tee -a /etc/hosts

# 访问应用
open http://php-vue.local

八、维护与优化

8.1 常用维护命令

# 查看Pod状态
kubectl get pods -n php-vue-prod -w

# 查看日志
kubectl logs -f deployment/php-backend -n php-vue-prod

# 进入容器
kubectl exec -it php-backend-xxxxx -n php-vue-prod -- bash

# 删除命名空间
kubectl delete namespace php-vue-prod

8.2 性能优化建议

  1. 为PHP-FPM配置OPcache
  2. 使用Redis缓存
  3. 前端启用HTTP/2和Gzip压缩
  4. 配置PHP和Nginx的合理资源限制

通过以上方案,您可以在Minikube上实现PHP+Vue项目的完整持续化部署流程,从代码提交到生产部署全自动化,同时保证开发、测试和生产环境的一致性。