使用 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 性能优化建议
- 为PHP-FPM配置OPcache
- 使用Redis缓存
- 前端启用HTTP/2和Gzip压缩
- 配置PHP和Nginx的合理资源限制
通过以上方案,您可以在Minikube上实现PHP+Vue项目的完整持续化部署流程,从代码提交到生产部署全自动化,同时保证开发、测试和生产环境的一致性。
No Comments