1. 项目概述与核心价值

如果你正在寻找一个能让你快速启动一个现代化、可运维、且具备企业级部署能力的Drupal项目模板,那么 wunderio/drupal-project 绝对值得你花时间深入研究。这个项目远不止是一个简单的 composer create-project 命令的替代品,它是一个经过实战打磨的、为 Kubernetes 集群部署而生的完整解决方案。我接触过不少 Drupal 项目模板,但这个模板在开发体验、代码质量管控和持续集成/持续部署(CI/CD)流程的整合上,其完整性和成熟度都令人印象深刻。

简单来说,这个模板解决了 Drupal 开发者,特别是团队协作和需要频繁部署到复杂环境(如 Kubernetes)的开发者,面临的几个核心痛点:如何统一开发环境、如何自动化代码质量检查、如何安全地管理不同环境的配置和密钥、以及如何构建一个可靠且可重复的部署流水线。它基于业界广泛使用的 drupal-composer/drupal-project ,但在此基础上,深度集成了 Wunder 团队在大量 Drupal 项目中积累的最佳实践和工具链,包括 DDEV 本地开发环境、CircleCI 自动化流水线、以及专为 Kubernetes 设计的 Silta 部署配置。

对于个人开发者,它能帮你建立起一套非常专业的开发规范;对于团队,它几乎提供了一个“开箱即用”的标准化项目骨架,能极大减少项目初期的配置成本,并确保所有成员在统一的工具和流程下协作。接下来,我将为你深入拆解这个项目的设计思路、核心配置以及在实际使用中需要注意的细节和技巧。

2. 项目架构与核心组件解析

2.1 核心设计哲学:一切皆代码,环境即配置

wunderio/drupal-project 的核心设计思想是“基础设施即代码”和“配置即代码”的实践。这意味着,从本地开发环境(DDEV)到云端生产环境(Kubernetes via Silta)的所有配置,都通过版本控制的文件来定义和管理。这种做法的最大好处是 可重复性和一致性 。新成员加入项目,只需 git clone 和几条命令,就能获得一个与生产环境高度一致的本地开发环境,彻底告别“在我机器上是好的”这类问题。

项目结构清晰地体现了这一思想:

  • .ddev/ : 定义了完整的本地容器化开发环境,包括 Web 服务器、数据库、缓存、搜索服务等。
  • silta/ : 包含了部署到 Kubernetes 集群(Silta)所需的所有 Helm Chart 配置和加密的密钥文件。这是将 Drupal 应用“容器化”并部署到云上的蓝图。
  • .circleci/ : 定义了从代码提交到自动构建、测试、部署的完整 CI/CD 流水线。
  • grumphp.yml : 代码提交时的质量门禁,确保不符合规范的代码无法进入仓库。
  • composer.json : 不仅管理 PHP 依赖,还通过 drupal-scaffold 等插件管理 Drupal 核心文件,并通过 scripts 字段定义项目级别的 Composer 钩子。

这种结构将开发、部署、运维的关切点分离,但又通过配置文件紧密连接。开发者主要关注 .ddev 和代码本身,运维或 DevOps 工程师则关注 silta .circleci 的配置。所有改动都经过代码审查,确保了变更的可追溯性。

2.2 核心工具链选型与优势

模板集成的每一个工具都不是随意选择的,背后都有其解决特定问题的考量:

  1. DDEV 作为本地开发环境 :相比 Lando 或单纯的 Docker Compose,DDEV 对 Drupal 的生态支持更为原生和友好。它提供了大量针对 Drupal 优化的命令(如 ddev drush ddev syncdb ),并且其插件系统(如 ddev-wunderio-drupal )可以无缝扩展功能。选择 DDEV 意味着团队可以快速获得一个功能齐全、性能接近生产的本地环境,包括 Varnish、Elasticsearch、Mailpit 等常用服务。

  2. CircleCI 作为 CI/CD 平台 :CircleCI 与 GitHub 集成度极高,配置采用 YAML 文件,清晰易懂。模板中预设的流水线已经处理了复杂的环境部署(包括多集群),并集成了密钥管理、手动审批等企业级功能。使用它,团队可以专注于定义“构建什么”和“部署到哪里”,而无需从头搭建 Jenkins 等更重型的系统。

  3. Silta 作为 Kubernetes 部署方案 :Silta 是 Wunder 基于 Helm 为 Drupal 定制的 Kubernetes 部署方案。它封装了 Drupal 在 K8s 上运行的最佳实践,如 ConfigMap 管理、Secret 注入、水平自动伸缩、健康检查等。 silta/ 目录下的 YAML 文件就是 Helm values.yaml 的定制化版本,让你能用声明式的方式定义生产环境的状态。这对于需要在多个客户或环境中维护数十个 Drupal 站点的团队来说,是维持部署一致性的关键。

  4. GrumPHP 与 Conventional Commits :强制性的代码提交规范(必须包含 JIRA/GitHub 票据 ID)和自动化检查(代码风格、语法),是在团队规模扩大后维持代码库健康度的基石。它把质量控制左移,在代码提交时就发现问题,比在 CI 阶段甚至生产环境才发现问题的成本低得多。

实操心得 :这套工具链的组合,初看可能觉得复杂,但一旦跑通,其带来的效率提升和风险降低是巨大的。特别是 ddev start 一键拉起所有服务和 git push 后自动部署到测试环境的能力,能让开发团队更专注于业务逻辑开发,而非环境调试。

3. 从零开始:项目初始化与配置详解

3.1 创建与克隆项目仓库

第一步是使用 GitHub 的“Use this template”功能。这里有一个 关键细节 :项目命名规范 client-COUNTRYCODE-CLIENT-PROJECT 。这不仅仅是命名约定,它通常与后续的 Kubernetes 命名空间、域名生成规则相关联。例如,一个名为 client-us-acme-website 的项目,其开发环境的域名可能就会是 main.client-us-acme-website.dev.wdr.io 。遵循这个规范能避免后续在 CI/CD 和 DNS 配置上出现意外问题。

克隆项目后,不要急于启动。首先进行一轮“项目身份”的配置更新:

  1. README.md : 将模板的通用说明替换为当前项目的具体信息,如项目简介、本地开发指南、部署流程等。这是给未来团队成员(包括你自己)的第一份文档。
  2. composer.json : 更新 name description 字段。更重要的是,检查 require require-dev 中的依赖版本,根据项目实际需要(如 Drupal 核心版本、关键模块版本)进行调整。
  3. grumphp.yml : 找到 git_commit_message 任务下的正则表达式。你需要将其中匹配票据 ID 的模式(如 [A-Z]+-\d+ )更新为你们项目实际使用的票据系统格式。例如,如果你们用 JIRA 且项目键是 WEB ,那么正则表达式可能需要包含 [WEB]-\d+ 。这一步至关重要,否则所有提交都会因票据格式不符而被 GrumPHP 拦截。

3.2 深度定制本地 DDEV 环境

.ddev/config.yaml 是 DDEV 环境的核心。模板提供了基础配置,但你需要根据项目需求进行调整:

  • name : 项目名称,会影响本地访问域名( <name>.ddev.site )。建议保持与仓库名一致以避免混淆。
  • php_version : 确保与生产环境计划使用的 PHP 版本一致。
  • webserver_type : 通常是 nginx-fpm ,这是 Drupal 的推荐搭配。
  • database : 默认是 mysql:8.0 ,如果生产环境用 MariaDB,可以考虑切换。
  • xdebug_enabled : 默认为 false 。我建议在 config.yaml 中保持关闭,而在需要时通过 ddev xdebug on 临时开启。因为 Xdebug 会显著降低 PHP 执行速度,影响日常开发体验。
  • additional_hostnames additional_fqdns : 如果你的站点需要响应多个本地域名(例如,用于多语言或多站点的测试),可以在这里添加。

更高级的定制在 .ddev/docker-compose.*.yaml 文件中。例如,模板中已经包含了 docker-compose.elasticsearch8.yaml 来提供 Elasticsearch 服务。如果你需要 Redis 作为缓存后端,就可以创建一个 docker-compose.redis.yaml 文件来定义 Redis 服务。这种覆盖式配置让服务管理非常灵活。

关于 ddev-wunderio-drupal 插件 :这个插件是模板的灵魂组件之一。它通过一个 pre-start 钩子自动安装。它提供了 ddev syncdb (从远程环境同步数据库)、 ddev grumphp ddev phpunit 等关键命令。查看其 GitHub 仓库的文档,可以了解所有可用命令和配置选项。例如, syncdb 命令通常需要你先通过 ddev auth ssh 认证到远程主机,并且项目 VPN 配置正确才能工作。

3.3 配置管理与环境分离

Drupal 的配置管理( config/sync )是强大但容易踩坑的功能。模板通过 config_split 模块来实现不同环境的配置差异化。

  1. 理解默认结构 :在 web/sites/default/settings.php 中,你会看到引用了 config_split 的设置。通常会有 config_split.config_split.silta .production .main .local 等配置项。
  2. 环境逻辑
    • local : 本地开发环境独有配置(如禁用前端聚合、启用开发模块)。
    • main : 对应于 main (开发/集成)环境。
    • production : 生产环境配置。
    • silta : 所有 Silta 部署的 K8s 环境共享的配置(如 Varnish 设置、文件系统路径)。
  3. 如何操作 :在本地开发时,你通过 drush cex 导出的配置是“完整配置”。 config_split 会根据当前环境(由 settings.php 中的条件判断决定)自动激活对应的“分割配置”,并在导入时( drush cim )合并。这意味着,你可以在本地启用 devel 模块并导出配置,只要它被分配到 config_split.config_split.local 中,那么这部分配置就不会被同步到生产环境。
  4. 关键步骤 :初始化新站点后,你需要先在 UI 上( /admin/config/development/configuration/config_split )或使用 Drush 命令启用这些 config_split 配置项,然后将它们的状态( status )导出到 config/sync 目录。这样,环境分离的“开关”本身也纳入了版本控制。

注意事项 config_split 的配置优先级很高。务必在项目早期就规划好哪些配置属于哪个环境。一个常见的错误是将生产环境的第三方 API 密钥放到了全局配置中,导致被意外同步到开发环境。安全的做法是:将所有环境敏感的配置(如 API 端点、密钥)通过环境变量注入,并在 settings.php 中根据环境读取,而不是存储在 config/sync 里。

4. CI/CD 流水线:CircleCI 与密钥管理实战

4.1 CircleCI 配置解析

.circleci/config.yml 文件定义了一个多工作流(workflow)的管道。其典型流程如下:

  1. build 任务 :在任何分支推送时触发。运行 composer install npm install (如果存在)、执行代码质量检查(如 GrumPHP,实际上在提交时已检查,这里可能做二次验证)和 PHPUnit 测试。这是一个质量关卡,构建或测试失败会阻止后续部署。
  2. deploy-to-silta 任务 :此任务负责将应用部署到 Silta 集群。它依赖于 build 任务的成功。
    • 环境判断 :任务会通过分支名判断目标环境。通常, main 分支部署到 main (开发)环境, production prod 分支部署到 production (生产)环境。 feature/* 分支可能会部署到临时的预览环境。
    • 手动批准 :对于 production 环境的部署,模板通常配置了手动批准步骤( approve-deployment )。这需要在 CircleCI 的 UI 上点击“Approve”按钮后,部署才会继续。这是一个重要的安全阀。
    • 密钥注入 :部署任务会使用对应环境的加密密钥,解密 silta/silta*.secrets 文件,并将这些密钥作为环境变量注入到 Kubernetes 的 Secret 中,供 Drupal 容器使用。

4.2 密钥管理:最需要谨慎对待的环节

这是整个模板中 安全风险最高 、也最需要理解透彻的部分。原理是“加密后再提交”,确保敏感信息(数据库密码、API 密钥等)不会以明文形式出现在 Git 历史中。

步骤详解:

  1. 生成加密密钥 :在 CircleCI 项目设置的 Contexts 中,为 silta_dev silta_finland (或其他环境)分别创建加密密钥。这个密钥是一个长字符串。在 CircleCI UI 中,它被存储为一个环境变量,例如 SEC_CLIENT_US_ACME_WEBSITE_SILTA_DEV 务必立即将此密钥备份到团队的密码管理器(如 LastPass、1Password)中! 如果丢失,对应的加密文件将无法解密,导致部署失败。
  2. 配置 CircleCI :在 .circleci/config.yml 中,找到 deploy-to-silta 任务,你会看到类似 secret_key_env: SEC_CLIENT_US_ACME_WEBSITE_SILTA_DEV 的引用。这告诉 CircleCI 在运行时使用哪个环境变量作为解密密钥。
  3. 创建 secrets 文件 :在本地,你有一个 silta/silta.secrets.example 文件作为模板。复制它并重命名为 silta/silta.secrets (用于开发环境)和 silta/silta-prod.secrets (用于生产环境)。在这些 YAML 文件中,以明文形式填写该环境所需的密钥。
    # silta/silta.secrets 示例
    env:
      DRUPAL_HASH_SALT: 'your_very_long_random_string_here'
      DATABASE_PASSWORD: 'dev_db_password'
      SOME_API_KEY: 'dev_api_key_123'
    
  4. 加密并提交 :使用 Silta CLI 工具进行加密。
    # 假设你已安装 silta-cli
    # 加密开发环境 secrets 文件
    silta secrets encrypt --file silta/silta.secrets --secret-key=$SEC_CLIENT_US_ACME_WEBSITE_SILTA_DEV
    # 加密后,会生成 silta/silta.secrets.encrypted 文件
    # 将 .encrypted 文件提交到 Git,并确保 .secrets(明文)文件在 .gitignore 中!
    
    加密后,原始的明文 .secrets 文件必须被 .gitignore 忽略,只提交 .encrypted 文件。CircleCI 在部署时会用对应的密钥解密它。
  5. 解密(用于本地调试或更新) :如果需要修改 secrets,你必须先解密。
    silta secrets decrypt --file silta/silta.secrets.encrypted --secret-key=$SEC_CLIENT_US_ACME_WEBSITE_SILTA_DEV
    # 这会重新生成明文的 silta/silta.secrets 文件
    

踩坑实录 :我曾遇到过因为团队成员在本地加密时使用了错误的密钥上下文(比如用生产密钥加密了开发配置文件),导致部署到开发环境时解密失败。 最佳实践是:在团队文档中明确记录每个环境对应的加密密钥变量名,并在执行加密命令前,通过 echo $SEC_* 确认当前 Shell 环境中的密钥变量是正确的。 另外,永远不要在 CI/CD 日志中打印密钥或解密后的内容,即使日志是私有的。

5. 高级开发技巧与故障排查

5.1 利用 Cursor AI 规则提升效率

模板内置了对 Cursor AI 编辑器的支持,规则文件位于 @.cursor/rules/ 。这些规则文件(如 common.mdc )本质上是在“教导” AI 助手关于你这个特定项目的上下文:文件结构、技术栈(Drupal、Silta、DDEV)、代码规范、提交信息格式等。

你可以这样利用它:

  • 快速生成代码 :当你想创建一个新的自定义模块时,可以直接问 Cursor:“基于我们项目的 Drupal 标准,创建一个名为 my_custom_module 的 .info.yml 文件和 .module 文件骨架。” Cursor 会根据规则中定义的模式来生成代码,可能自动包含正确的文件头注释和符合规范的代码结构。
  • 理解复杂配置 :如果你对 silta/values.yaml 中某个 Helm 参数的作用不清楚,可以选中它并询问 Cursor:“这个 resources.requests.memory 在 Silta 的上下文中是什么意思?生产环境通常设置多少?” AI 可以结合项目规则和其知识库给出更贴近你项目实践的答案。
  • 遵守提交规范 :当你撰写提交信息时,Cursor 可以根据 grumphp.yml 中定义的规则,提示你正确的票据 ID 格式和提交类型(feat, fix等)。

自定义规则 :随着项目发展,你可以将团队达成共识的编码习惯、常用的代码片段、甚至是常见的错误解决方案添加到 @.cursor/rules/ 下的自定义规则文件中,让 AI 助手成为团队知识库的延伸。

5.2 Varnish 与 Purge 缓存配置实战

在本地使用 Varnish 进行缓存测试是确保生产环境缓存行为正确的关键。模板已经做了大量预配置,但理解其工作原理能帮你更好地调试。

  1. 配置核心 :核心是 varnish_purger 模块和 purge 套件。确保它们已启用 ( drush en varnish_purger purge )。关键的配置在 admin/config/development/performance/purge
  2. Purger 设置要点
    • 类型 :选择 “Tags”。这是 Drupal 8/9/10 推荐的缓存失效方式,基于缓存标签(cache tags)的粒度更细。
    • 请求方法 必须选择 BAN 。这是模板和 Silta 环境特意强调的一点。Silta 的 Varnish 配置通常只允许 BAN 请求来清除缓存,使用 PURGE 方法会返回 405 Method Not Allowed 。这是一个容易忽略但会导致缓存无法清除的坑。
    • 请求头 :设置为 Cache-Tags: [invalidation:expression] 。这告诉 Varnish,请求头中的 Cache-Tags 字段值就是需要失效的缓存标签表达式。
  3. 环境变量注入 :在 web/sites/default/settings.php 中,有一段代码根据 VARNISH_ADMIN_HOST 环境变量来动态设置 Varnish 的主机和端口。在 DDEV 环境中,这些变量已被预设好,指向容器内的 varnish 服务。在生产环境的 Silta 部署中,这些值也会由 Kubernetes 自动注入。 这意味着同一套配置( config/sync 中的)可以在不同环境下无缝工作 ,这是环境变量和配置分离带来的好处。
  4. 测试缓存清除
    # 在 DDEV 环境中测试 BAN 请求
    ddev exec curl -v -X BAN -H "Cache-Tags: config:system.performance" http://varnish
    
    如果返回 200 Ban added ,说明 Varnish 接收并处理了请求。你可以接着访问一个页面,触发 Drupal 的缓存重建,然后观察该页面的缓存是否被正确清除。
  5. 查看 Varnish 日志
    ddev exec -s varnish varnishlog -i BAN -i Cache
    
    这个命令可以实时过滤出与 BAN 请求和缓存操作相关的日志,是调试缓存失效问题的利器。

5.3 数据库同步与工作流整合

ddev syncdb 命令是 ddev-wunderio-drupal 插件提供的“杀手级”功能。它通常通过 SSH 连接到远程环境(如 main ),导出数据库,并导入到本地。这保证了本地开发使用的是最新、最真实的数据。

实现前提

  1. SSH 访问权限 :你需要有访问远程 Kubernetes Pod 或跳板机的 SSH 密钥,并且该密钥已添加到你的本地 SSH Agent。 ddev auth ssh 命令就是用来处理容器内 SSH 认证的。
  2. 网络可达 :通常意味着你需要连接到公司的 VPN,才能访问开发或测试环境的内部网络。
  3. 正确的 Drush 别名 :远程环境的 Drush 站点别名(如 @main )必须配置正确,并且本地 DDEV 容器能够通过该别名连接到远程数据库。

常见问题排查

  • 错误: Failed to connect to remote host :首先确认 VPN 连接正常。然后在 DDEV 容器内尝试 ssh www-admin@main-shell... 看是否能连通。可能是 SSH 密钥未加载或远程主机密钥变更。
  • 错误: Drush command failed :检查远程环境的 Drush 别名文件(通常是 ~/.drush/sites/ 下的 YAML 文件)是否存在且配置正确。特别是数据库连接信息。
  • 同步速度慢 :如果数据库很大,同步会耗时很长。可以考虑在远程环境使用 drush sql:dump --extra-dump=--skip-lock-tables --gzip 生成压缩的导出文件,或者在本地同步时使用 --skip-tables-key=common 来跳过一些非核心的大表(如缓存表、日志表)。

工作流建议 :在开始一天的工作或切换任务分支前,先运行 ddev syncdb main 同步最新的开发环境数据库。在功能开发完成后,运行 ddev drush deploy (它依次执行 config:import , updatedb , cache:rebuild )来确保配置和数据库更新在本地无误,然后再提交代码。

5.4 测试策略与 PHPUnit 集成

模板预置了 PHPUnit 配置( phpunit.xml )和一个示例模块( web/modules/custom/phpunit_example )。这为实施测试驱动开发(TDD)或编写单元/功能测试打下了基础。

配置要点

  • phpunit.xml 中已经设置了 Drupal 测试所需的 bootstrap 文件和测试套件。
  • 它通常将 web/core web/modules/custom 等目录包含在测试扫描范围内。

运行测试

# 运行所有测试(可能很慢)
ddev phpunit
# 运行特定自定义模块下的测试
ddev phpunit web/modules/custom/my_module/tests
# 运行一个特定的测试类
ddev phpunit web/modules/custom/my_module/tests/src/Unit/MyServiceTest.php
# 运行标记为 “kernel” 的测试组(内核测试,比完整功能测试快)
ddev phpunit --group kernel

编写测试的建议

  1. 目录结构 :在自定义模块下建立 tests/src/Unit/ (单元测试)和 tests/src/Kernel/ (内核测试)或 tests/src/Functional/ (功能测试)目录。
  2. 命名空间 :测试类的命名空间应与被测试类对应,并以 Test 结尾。例如, \Drupal\my_module\MyService 的测试类应为 \Drupal\Tests\my_module\Unit\MyServiceTest
  3. 利用 Drupal 测试基类 :单元测试继承 \Drupal\Tests\UnitTestCase ,内核测试继承 \Drupal\KernelTests\KernelTestBase ,功能测试继承 \Drupal\Tests\BrowserTestBase 。这些基类提供了模拟容器、数据库事务、浏览器会话等基础设施。

集成到 CI .circleci/config.yml 中的 build 任务应该包含运行测试的步骤。一个常见的模式是:先运行快速的单元测试和内核测试,如果通过,再运行更耗时的功能测试。确保测试失败会令 CI 任务失败,从而阻止有问题的代码被部署。

6. 常见问题与排查技巧实录

在实际使用这个模板的过程中,你几乎一定会遇到一些问题。下面是我和团队总结的一些常见问题及其解决方法。

6.1 环境启动与依赖问题

问题: ddev start 失败,提示端口被占用或 Docker 错误。

  • 排查 :首先运行 ddev poweroff 关闭所有 DDEV 项目,然后 ddev start 重试。如果问题依旧,检查 Docker Desktop 是否正常运行(系统托盘图标)。在 macOS/Linux 上,可以尝试 docker system prune -a --volumes 警告:这会清除所有未使用的 Docker 数据,包括其他项目的数据库 )来清理可能冲突的容器或卷,然后重启 Docker。

问题:Composer 安装依赖时内存不足或超时。

  • 排查 :DDEV 默认给 PHP 容器的内存可能有限。你可以通过增加 Docker Desktop 的资源分配(Settings -> Resources)来解决。对于超时,可以尝试在 ddev start 前设置 Composer 镜像或使用 ddev composer install --no-scripts --no-interaction 先跳过脚本安装核心依赖,然后再手动处理。

6.2 部署与 CI/CD 问题

问题:CircleCI 部署任务失败,错误信息模糊,显示“Error deploying to Silta”。

  • 排查步骤
    1. 查看完整日志 :在 CircleCI Job 页面,展开所有步骤,尤其是运行 silta deploy 命令的那一步,查看其完整输出。
    2. 检查加密密钥 :这是最常见的原因。确认 CircleCI Context 中对应的 SEC_* 环境变量值是否正确,且与加密 silta*.secrets.encrypted 文件时使用的密钥一致。可以尝试在本地用该密钥解密文件,看是否能成功。
    3. 检查 Helm/Kubernetes 配置 :错误可能源于 silta/values.yaml 中的配置错误,比如镜像名称错误、资源请求( resources )设置过高导致集群资源不足、或缺少必要的配置项。对比一个成功部署的项目的 values.yaml
    4. 检查集群权限 :确认用于部署的 Service Account 或 Kubeconfig 在目标 Kubernetes 集群中有足够的权限创建/更新指定命名空间中的资源。

问题:代码提交被 GrumPHP 拒绝,提示提交信息格式错误。

  • 排查 :仔细阅读错误信息。GrumPHP 会指出具体哪里不符合正则表达式。确保你的提交信息严格遵循 [TICKET-123]: (type) Description 的格式。票据 ID 必须在方括号内,类型必须在圆括号内,后面跟一个空格和描述。描述首字母大写,结尾不要有句点。例如: [WEB-456]: (fix) Resolve login issue for users with special characters

6.3 本地开发与功能问题

问题: ddev syncdb 失败,提示 SSH 或数据库连接错误。

  • 排查
    1. SSH 连接 :在 DDEV 容器内手动执行 SSH 命令: ddev ssh 进入容器,然后尝试 ssh www-admin@main-shell.... 。如果失败,检查 ddev auth ssh 是否执行过,以及本地 ~/.ssh 的密钥是否已加载到 agent ( ssh-add -l )。
    2. Drush 别名 :确认远程环境的 Drush 别名文件存在且路径正确。有时别名文件可能位于 /var/www/.drush/sites/ 而非用户目录。
    3. 网络/VPN :确保你连接到了可以访问目标内部网络(如 dev.wdr.io )的 VPN。

问题:本地站点访问缓慢,或 Xdebug 导致页面超时。

  • 排查
    • 性能 :检查是否无意中开启了 Xdebug ( ddev xdebug status )。如果是,用 ddev xdebug off 关闭它。Xdebug 对性能影响极大,仅调试时开启。
    • 资源 :使用 ddev describe 查看服务状态,或用 docker stats 查看容器资源使用情况。如果 MySQL 或 Elasticsearch 容器占用 CPU 过高,可能是查询问题或索引重建。
    • OPCache :确保 opcache.enable=1 在 PHP 配置中已启用,这是生产模式下的重要性能优化。

问题:配置导入( drush cim )时发生冲突或错误。

  • 排查
    1. 先导出再导入 :在另一环境做了配置更改后,确保已通过 drush cex 导出并提交。在本地,先 git pull 获取最新配置,再运行 drush cim
    2. 使用 --partial --source :如果只是部分配置冲突,可以尝试 drush cim --partial --source=../config/sync --partial 允许导入部分配置,但需谨慎使用。
    3. 检查 UUID :确保 web/sites/default/config 目录下的 system.site 配置的 UUID 与数据库中的 UUID 一致。如果不一致,可以用 drush config:set system.site uuid [new-uuid] 来同步。
    4. 查看详细错误 :使用 drush cim -y --verbose 获取更详细的错误信息,通常会精确到是哪个配置项的哪一行出了问题。

6.4 缓存与 Varnish 问题

问题:内容更新后,前端页面看不到变化,怀疑缓存未清除。

  • 排查流程
    1. 确认 Varnish 是否运行 ddev describe 查看 varnish 服务状态。
    2. 手动触发清除 :在 Drupal 后台 ( admin/config/development/performance ) 点击“清除所有缓存”,或运行 ddev drush cache:rebuild
    3. 检查 Purger 队列 :访问 admin/config/development/performance/purge ,查看“队列”选项卡,看是否有积压的失效项。可以尝试手动处理队列。
    4. 测试 BAN 请求 :如前面所述,用 curl 命令直接测试 Varnish 的 BAN 接口,看是否返回成功。
    5. 查看 Varnish 日志 :使用 ddev exec -s varnish varnishlog -g request -q "ReqURL ~ '^/node/'" (替换为你更新的页面路径)来查看对该页面的请求是否命中了缓存,以及是否有 BAN 请求记录。
    6. 检查缓存标签 :确保你更新的实体(如节点)正确地设置了缓存标签,并且 Purge 配置正确订阅了这些标签的失效事件。

通过系统性地使用这个模板并理解其各部分的工作原理,你不仅能快速搭建起一个健壮的 Drupal 项目,更能掌握一套现代化的、可扩展的 Drupal 开发和部署方法论。它强迫你遵循最佳实践,虽然初期学习曲线稍陡,但长期来看,它为项目的可维护性、团队协作效率和部署可靠性带来的收益是巨大的。

更多推荐