现代 Laravel 开发需要一条健壮的 CI/CD 流水线来自动化部署、数据库迁移与基础设施操作。如果你正受困于手工部署、环境不一致,或担心在生产环境执行 php artisan migrate
的风险,这篇文章将给出一套可落地的端到端方案。
当你的生产环境运行在 Kubernetes 上,同时又希望 GitLab CI、Jenkins 与 n8n 无缝协作时,挑战会显著增加:工具链割裂、流程半自动、以及“别把生产搞挂了”的心理负担。本指南将带你构建一条完整的 Laravel CI/CD 流水线:在合并到 master
后自动触发生产数据库迁移,由 Jenkins 编排 n8n 工作流,在 Kubernetes 中安全执行 kubectl
操作。
整套 CI/CD 架构由四个组件协同工作:
master
的变更。若你使用 GitLab 进行版本控制,它在代码集成方面具有天然优势。流程是:每次合并到 master
,GitLab CI 发现变更 → Jenkins 接收触发 → n8n 执行部署工作流 → 在 Kubernetes 中运行 kubectl
命令进行镜像更新、回滚与数据库迁移。
看似复杂,但“各司其职”能带来强健性与灵活性:GitLab CI 负责集成与测试,Jenkins 提供成熟的构建与插件生态,n8n 以可视化方式表达复杂决策与异常路径,Kubernetes 则是生产级容器编排底座。职责清晰、边界明确,使系统具备可维护性与可演进性。
.gitlab-ci.yml
在项目根目录新增 .gitlab-ci.yml
定义阶段与触发规则:
stages:
- test
- build
- deploy
variables:
JENKINS_URL: 'https://your-jenkins-instance.com'
JENKINS_JOB: 'laravel-production-deploy'
test:
stage: test
image: php:8.2
before_script:
- apt-get update -qq && apt-get install -y -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install --no-dev --no-scripts
script:
- cp .env.example .env
- php artisan key:generate
- php artisan test
only:
- master
- merge_requests
测试阶段是质量门禁。在深入学习 Laravel 测试策略时,可参考这篇指南:Laravel testing with unit, feature, and integration tests。
手动触发生产部署:
deploy_production:
stage: deploy
script:
- |
curl -X POST \
-H "Authorization: Bearer $JENKINS_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"parameter\": [{\"name\": \"GIT_COMMIT\", \"value\": \"$CI_COMMIT_SHA\"}, {\"name\": \"BRANCH\", \"value\": \"$CI_COMMIT_REF_NAME\"}]}" \
"$JENKINS_URL/job/$JENKINS_JOB/buildWithParameters"
only:
- master
when: manual
为更精细的可观测性与审计,将提交信息、分支、流水线 ID 与环境等元数据传给 Jenkins:
deploy_production:
stage: deploy
variables:
DEPLOYMENT_METADATA: |
{
"commit_sha": "$CI_COMMIT_SHA",
"commit_message": "$CI_COMMIT_MESSAGE",
"branch": "$CI_COMMIT_REF_NAME",
"pipeline_id": "$CI_PIPELINE_ID",
"environment": "production",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
script:
- echo "$DEPLOYMENT_METADATA" > deployment_metadata.json
- |
curl -X POST \
-H "Authorization: Bearer $JENKINS_API_TOKEN" \
-H "Content-Type: application/json" \
-F "metadata=@deployment_metadata.json" \
"$JENKINS_URL/job/$JENKINS_JOB/buildWithParameters"
新建 Jenkins Pipeline Job 接收来自 GitLab CI 的触发,负责通过 n8n 编排整个部署过程。
建议安装插件:HTTP Request Plugin、Build Authorization Token Root Plugin、Pipeline Plugin。它们使 Jenkins 能接受 GitLab 的远程触发,并向 n8n 发起 HTTP 请求。
接受 GitLab 传入的参数并触发 n8n 工作流:
pipeline {
agent any
parameters {
string(name: 'GIT_COMMIT', defaultValue: '', description: 'Git commit SHA')
string(name: 'BRANCH', defaultValue: 'master', description: 'Git branch')
string(name: 'ENVIRONMENT', defaultValue: 'production', description: 'Deployment environment')
}
environment {
N8N_WEBHOOK_URL = credentials('n8n-webhook-url')
KUBE_CONFIG = credentials('kubernetes-config')
}
stages {
stage('Validate Parameters') {
steps {
script {
if (!params.GIT_COMMIT) {
error('Git commit SHA is required')
}
echo "Deploying commit ${params.GIT_COMMIT} to ${params.ENVIRONMENT}"
}
}
}
stage('Trigger n8n Workflow') {
steps {
script {
def payload = [
git_commit: params.GIT_COMMIT,
branch: params.BRANCH,
environment: params.ENVIRONMENT,
jenkins_job: env.JOB_NAME,
jenkins_build: env.BUILD_NUMBER
]
def response = httpRequest(
url: "${N8N_WEBHOOK_URL}",
httpMode: 'POST',
contentType: 'APPLICATION_JSON',
requestBody: groovy.json.JsonBuilder(payload).toString(),
timeout: 300
)
if (response.status != 200) {
error("n8n workflow failed with status: ${response.status}")
}
echo "n8n workflow triggered successfully"
echo "Response: ${response.content}"
}
}
}
stage('Monitor Deployment') {
steps {
script {
// Add monitoring logic here
echo "Monitoring deployment progress..."
sleep(30)
}
}
}
}
post {
success {
echo "Deployment completed successfully"
}
failure {
echo "Deployment failed"
}
}
}
在 n8n 中新建工作流:通过 Webhook 接收 Jenkins 触发,编排 Kubernetes 部署,包含异常处理与回滚。
建议节点:Webhook(接收)、Function(数据处理)、HTTP Request(发 kubectl
指令或调用 API 网关)、IF/Condition(校验)、Slack/Email(通知)。
// 处理 Jenkins 传入的 JSON
const deploymentData = {
gitCommit: $json.git_commit,
branch: $json.branch,
environment: $json.environment,
jenkinsJob: $json.jenkins_job,
jenkinsBuild: $json.jenkins_build,
timestamp: new Date().toISOString()
}
if (!deploymentData.gitCommit) {
throw new Error('Git commit SHA is required')
}
if (deploymentData.environment !== 'production') {
throw new Error('Only production deployments are supported')
}
return {
deployment: deploymentData,
kubeNamespace: 'laravel-prod',
migrationCommand: 'php artisan migrate --force'
}
// 组装需要执行的 kubectl 命令
const kubeCommands = [
`kubectl set image deployment/laravel-app laravel-app=your-registry/laravel:${$json.deployment.gitCommit} -n ${$json.kubeNamespace}`,
`kubectl rollout status deployment/laravel-app -n ${$json.kubeNamespace} --timeout=300s`,
`kubectl exec -n ${$json.kubeNamespace} deployment/laravel-app -- ${$json.migrationCommand}`,
`kubectl get pods -n ${$json.kubeNamespace} -l app=laravel-app --field-selector=status.phase=Running`
]
return {
commands: kubeCommands,
namespace: $json.kubeNamespace,
deployment: $json.deployment
}
// 统一错误处理:失败即触发回滚
if ($json.error) {
const rollbackCommands = [`kubectl rollout undo deployment/laravel-app -n ${$json.namespace}`, `kubectl rollout status deployment/laravel-app -n ${$json.namespace} --timeout=300s`]
return {
action: 'rollback',
commands: rollbackCommands,
originalError: $json.error,
deployment: $json.deployment
}
}
return {
action: 'success',
deployment: $json.deployment
}
apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-app
namespace: laravel-prod
labels:
app: laravel-app
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: laravel-app
template:
metadata:
labels:
app: laravel-app
spec:
containers:
- name: laravel-app
image: your-registry/laravel:latest
ports:
- containerPort: 80
env:
- name: APP_ENV
value: 'production'
- name: DB_HOST
valueFrom:
secretKeyRef:
name: laravel-secrets
key: db-host
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: laravel-secrets
key: db-password
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: '256Mi'
cpu: '250m'
limits:
memory: '512Mi'
cpu: '500m'
apiVersion: batch/v1
kind: Job
metadata:
name: laravel-migration-${BUILD_NUMBER}
namespace: laravel-prod
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migration
image: your-registry/laravel:${GIT_COMMIT}
command: ['php', 'artisan', 'migrate', '--force']
env:
- name: APP_ENV
value: 'production'
- name: DB_HOST
valueFrom:
secretKeyRef:
name: laravel-secrets
key: db-host
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: laravel-secrets
key: db-password
backoffLimit: 3
apiVersion: v1
kind: ServiceAccount
metadata:
name: laravel-deployer
namespace: laravel-prod
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: laravel-prod
name: laravel-deployment-manager
rules:
- apiGroups: ['apps']
resources: ['deployments']
verbs: ['get', 'list', 'watch', 'update', 'patch']
- apiGroups: ['batch']
resources: ['jobs']
verbs: ['create', 'get', 'list', 'watch', 'delete']
- apiGroups: ['']
resources: ['pods']
verbs: ['get', 'list', 'watch']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: laravel-deployment-binding
namespace: laravel-prod
subjects:
- kind: ServiceAccount
name: laravel-deployer
namespace: laravel-prod
roleRef:
kind: Role
name: laravel-deployment-manager
apiGroup: rbac.authorization.k8s.io
// n8n function:蓝绿切换
const currentEnvironment = $json.currentActive // 'blue' 或 'green'
const targetEnvironment = currentEnvironment === 'blue' ? 'green' : 'blue'
const deploymentCommands = [
`kubectl set image deployment/laravel-app-${targetEnvironment} laravel-app=your-registry/laravel:${$json.gitCommit} -n laravel-prod`,
`kubectl rollout status deployment/laravel-app-${targetEnvironment} -n laravel-prod --timeout=300s`,
`kubectl exec -n laravel-prod deployment/laravel-app-${targetEnvironment} -- php artisan migrate --force`,
`kubectl patch service laravel-service -n laravel-prod -p '{"spec":{"selector":{"version":"${targetEnvironment}"}}}'`
]
return {
commands: deploymentCommands,
targetEnvironment: targetEnvironment,
previousEnvironment: currentEnvironment
}
// 迁移前置检查与备份
const safetyChecks = [
`kubectl exec -n laravel-prod deployment/laravel-app -- php artisan migrate:status`,
`kubectl exec -n laravel-prod deployment/laravel-app -- php artisan backup:run --only-db`,
`kubectl exec -n laravel-prod deployment/laravel-app -- ls -la storage/app/backups/`
]
return {
checks: safetyChecks,
backupRequired: true,
migrationCommand: $json.migrationCommand
}
// 部署后健康检查
const monitoringChecks = [
{ type: 'http', url: 'https://your-app.com/health', expectedStatus: 200 },
{ type: 'kubectl', command: 'kubectl exec -n laravel-prod deployment/laravel-app -- php artisan tinker --execute="DB::connection()->getPdo();"' },
{ type: 'prometheus', query: 'up{job="laravel-app"}' }
]
return {
checks: monitoringChecks,
alertChannels: ['slack', 'email'],
deployment: $json.deployment
}
kubectl
失败 kubectl
。通过 Jenkins + n8n + GitLab CI 打造的 Laravel CI/CD,可以在生产安全地自动执行数据库迁移,保证发布一致与可追溯,让团队从“运维焦虑”回到“专注交付价值”。
这套架构的关键收益包括:
建议从最小可行流开始,逐步叠加蓝绿发布、监控告警与安全增强。现在就从 GitLab CI 与 Jenkins Job 起步,随后接入 n8n 与 Kubernetes 集成。