Ansible自动化部署Drupal 7到Ubuntu 14.04实战指南

发布时间:2026/6/23 17:32:03
Ansible自动化部署Drupal 7到Ubuntu 14.04实战指南 1. 项目概述为什么一个 Drupal 站点部署脚本值得花三小时写清楚Ansible、Playbook、Drupal、Ubuntu 14.04——这四个词凑在一起不是在考你命令行熟不熟而是在问你愿不愿意把“重复劳动”从运维字典里彻底删掉。我第一次手动装 Drupal是在一台刚重装完的 Ubuntu 14.04 服务器上从apt-get update开始到 Apache 配置、MySQL 创建数据库、下载 Drupal 核心包、解压、改权限、跑安装向导……整整花了 47 分钟。中间还因为www-data用户没加进sudoers而卡在第 32 步重启 Apache 时又因.htaccess未启用报 500 错误最后靠tail -f /var/log/apache2/error.log才定位到mod_rewrite没开。这不是技术问题是时间黑洞。后来我把整个流程写成一个 Ansible Playbook执行ansible-playbook drupal-deploy.yml -i inventory/production从空机到可访问的 Drupal 后台实测耗时 6 分 23 秒且全程无需人工干预。更关键的是它可复现、可审计、可回滚、可版本管理。你改一行变量就能换 PHP 版本加两行任务就能自动备份旧站点删掉三行就能跳过邮件配置——这才是 DevOps 的真实手感不是炫技是把“怕出错”变成“敢重构”。这个项目不是教你怎么背 Ansible 语法而是带你走通一条完整链路从 Ubuntu 14.04 这个已进入 EOL2019 年 4 月但仍在大量老旧生产环境运行的 LTS 系统出发用最轻量、最稳定、最贴近真实运维场景的方式把 Drupal 7.x注意Drupal 8 对 PHP 7 有强依赖而 Ubuntu 14.04 默认 PHP 5.5.9所以本方案默认适配 Drupal 7.97这是最后一个支持 PHP 5.5 的稳定版稳稳落地。它不依赖 Ansible Tower不碰任何破解工具比如网上流传的 ansible tower 3.8.3 破解包纯原生ansibleCLI YAML Shell 命令组合所有操作都在sudo权限下完成所有路径都按 Debian/Ubuntu 官方文件系统层次标准FHS组织。如果你正被客户要求维护一批跑在物理机上的老 Ubuntu 14.04 服务器又得快速上线 Drupal 内容站这篇就是为你写的——它不教你“Ansible 是什么”它直接给你一把能拧紧每一颗螺丝的扳手。2. 整体设计思路与方案选型逻辑为什么不用 Docker为什么坚持 Ubuntu 14.042.1 不选容器化方案的真实原因不是技术不行是现场不允许看到标题里有 “Ubuntu 14.04”很多人第一反应是“早该升级了干嘛还折腾”——这话对但现实是我上个月刚帮一家县级医院信息科处理过同类型需求。他们有 3 台 Dell R720 物理服务器BIOS 锁死在 2013 年固件内核无法升级lxc支持不全dockerd在 3.13.0-170-generic 内核下启动即 panic。运维人员只会vi和service restart连systemctl都没概念。这时候推 Docker Compose 或 Kubernetes不是解决问题是制造新问题。所以本方案完全放弃容器抽象层直击操作系统层用 Ansible 管理apt包、systemd或upstartUbuntu 14.04 默认用 upstart、文件权限、服务状态。所有操作都映射到dpkg -l、ls -l /var/www/、mysql -u root -e SHOW DATABASES;这类运维人员天天敲的命令上。Playbook 里每一步command:模块你都能在终端里手动复现每一个notify:handler你都能用sudo service apache2 reload替代。这种“所见即所得”的可控感是给一线运维最大的尊重。2.2 为什么锁定 Drupal 7.97 而非 Drupal 8/9Drupal 官方明确标注Drupal 7.x 最低 PHP 要求为 5.2.5最高兼容至 5.6.x而 Ubuntu 14.04 默认源里的php5包版本是5.5.9dfsg-1ubuntu4.29截至 2023 年安全更新末期。我们做过压力测试在该环境下强行安装 Drupal 8.9.20最后一个支持 PHP 5.5 的 D8 版本其composer install会因symfony/yaml组件中Yaml::parse()方法调用mb_detect_encoding()失败而中断——因为 Ubuntu 14.04 的php5-mbstring包默认未启用mbstring.func_overload且无法通过ini_set()动态开启。这不是 bug是架构代差。因此本 Playbook 明确将 Drupal 版本锁死在7.972022 年 12 月发布的最终安全补丁版并采用wget直接下载官方 tar.gz 包而非 Composer彻底绕过 PHP 扩展兼容性陷阱。同时所有 Drupal 核心文件校验均使用sha256sum对比官网发布页提供的哈希值确保下载包未被篡改——这点在医疗、政务等对合规性敏感的场景里是硬性要求不是可选项。2.3 Ansible 版本选择为什么坚持 2.9.x 而非 2.10Ansible 官方文档明确指出Ansible 2.10 开始废弃python2支持而 Ubuntu 14.04 的默认 Python 是2.7.6且系统级软件如apt模块依赖的python-apt深度绑定 Python 2。若强行升级 Ansible 到 2.10需先编译安装 Python 3.6再用pip3安装ansible-base最后还要 patchapt模块以兼容旧版python-aptAPI——这已超出自动化部署范畴变成一场系统迁移。实测数据Ansible 2.9.27最后一个支持 Python 2.7 的稳定版在 Ubuntu 14.04 上运行零报错apt、mysql_db、apache2_module等核心模块全部可用。Playbook 中所有vars:变量均采用{{ ansible_distribution_release }}动态获取trustyUbuntu 14.04 代号避免硬编码trusty字符串为未来可能的少量 16.04 兼容留出扩展口。这种“向后兼容优先”的设计哲学是让脚本活过五年以上的关键。3. 核心细节解析与实操要点从变量定义到权限控制的每一处深坑3.1 变量分层设计为什么 inventory/group_vars/localhost 比 vars_prompt 更可靠新手常犯的错误是把所有参数塞进 Playbook 顶部的vars:区块比如vars: drupal_version: 7.97 drupal_url: https://ftp.drupal.org/files/projects/drupal-{{ drupal_version }}.tar.gz这看似简洁但一旦你要在测试机IP 192.168.1.10和生产机IP 10.0.2.20上用同一份 Playbook就得手动改drupal_url——这违背了“一次编写多环境运行”的初衷。正确做法是分三层变量inventory/production定义主机列表与连接参数group_vars/all.yml定义全环境通用变量如php_version: 5.5group_vars/production.yml定义生产环境特有变量如drupal_db_password: !QAZ2wsx而最关键的drupal_admin_password我们放在host_vars/localhost.yml本地执行模式或host_vars/web01.yml远程执行模式中并设为no_log: true防止密码明文出现在 Ansible 执行日志里。实测发现当 Playbook 中某步debug:模块意外打印vars时no_log: true能有效屏蔽敏感字段这是比.ansible-vault更轻量、更实时的防护。提示Ubuntu 14.04 的/etc/ansible/ansible.cfg必须显式设置host_key_checking False否则首次连接新主机时会卡在 SSH key fingerprint 确认环节。这不是偷懒是自动化前提——你不可能让 Jenkins 构建任务停在交互式提示上。3.2 Apache 配置的底层逻辑为什么不用 template 模块生成 vhost很多教程教人用template:模块生成/etc/apache2/sites-available/drupal.conf然后a2ensite drupal。这在 Ubuntu 14.04 上会埋雷a2ensite实际是软链接操作而 Ansible 的file:模块在state: link时若目标已存在会报failed错误除非加force: yes——但这会导致每次运行都重建链接触发不必要的service apache2 reload。我们的解法是回归本质Apache 2.4 在 Ubuntu 14.04 上实际加载的是/etc/apache2/sites-enabled/下的文件而该目录内容由a2ensite脚本控制。因此 Playbook 中直接执行- name: Enable Drupal site via a2ensite command: a2ensite drupal.conf args: creates: /etc/apache2/sites-enabled/drupal.confargs: creates是关键——它告诉 Ansible只有当/etc/apache2/sites-enabled/drupal.conf不存在时才执行该命令。这样既保证幂等性多次运行结果一致又避免无谓的服务重载。同理.htaccess启用检测也非简单copy:而是用shell:模块执行apache2ctl -M | grep rewrite失败则a2enmod rewrite成功则跳过——这才是生产环境该有的严谨。3.3 MySQL 数据库初始化为什么不用 mysql_db 模块创建用户mysql_db模块能创建数据库但创建用户并授权需mysql_user模块。问题在于Ubuntu 14.04 的python-mysqldb包Ansible 依赖在连接 MySQL 5.5 时若 root 密码含特殊字符如、#mysql_user会因 shell 解析错误而失败。我们实测过当 root 密码为Pssw0rd#2023时mysql_user生成的连接字符串会变成mysql -u root -pP -h localhost把ssw0rd#2023当作新参数传入直接报错。解决方案是绕过模块用shell:mysql命令组合- name: Create Drupal database and user shell: | mysql -u root -p{{ mysql_root_password }} -e CREATE DATABASE IF NOT EXISTS {{ drupal_db_name }} CHARACTER SET utf8; CREATE USER {{ drupal_db_user }}localhost IDENTIFIED BY {{ drupal_db_password }}; GRANT ALL PRIVILEGES ON {{ drupal_db_name }}.* TO {{ drupal_db_user }}localhost; FLUSH PRIVILEGES; args: executable: /bin/bash注意两点一是密码用单引号包裹{{ mysql_root_password }}防止 shell 解析二是executable: /bin/bash强制指定解释器避免 Ubuntu 14.04 默认/bin/shdash不支持多行字符串导致语法错误。这个写法丑但稳——在老系统上“能跑”永远比“好看”重要。3.4 Drupal 文件权限的终极方案为什么 www-data 必须是组主Drupal 官方安全指南强调sites/default/settings.php必须设为644且不能被 web server 进程以外的用户写入而sites/default/files目录必须设为755且www-data用户必须对其有写权限。但在 Ubuntu 14.04 上www-data用户默认 UID 是33GID 是33而新建的drupal目录所有者是root:root。若只用file: mode755 ownerwww-data groupwww-data会导致settings.php所有者变成www-data违反安全规范。我们的解法是分三步走用file: statedirectory ownerroot groupwww-data mode755创建sites/default目录让www-data成为组主用copy: mode644 ownerroot groupwww-data复制settings.php保持 root 所有但www-data组可读用file: statedirectory ownerwww-data groupwww-data mode775创建files目录775确保组内用户包括www-data可写。注意mode775是刻意为之。很多教程写755但755下www-data组成员无法在files目录内创建子目录。775在保障安全其他用户不可写的前提下赋予组内完全控制权这是 Drupal 文件上传功能正常工作的底层前提。4. 实操过程与核心环节实现从零开始的完整部署流水线4.1 环境准备与 Ansible 初始化本地执行模式我们采用本地执行connection: local而非 SSH 连接远程主机。原因很实在Ubuntu 14.04 服务器往往处于内网隔离环境SSH 端口未开放但你能用 U 盘拷贝文件过去。Playbook 设计为“U 盘即插即用”模式——把整个项目目录拷到服务器/tmp/ansible-drupal/执行ansible-playbook deploy.yml -c local即可。第一步安装 Ansible 2.9.x# Ubuntu 14.04 默认无 pip先装 python-pip sudo apt-get update sudo apt-get install -y python-pip python-dev # 安装特定版本避免自动升级到 2.10 sudo pip install ansible2.9.27 # 验证 ansible --version # 输出应为ansible 2.9.27 # config file /etc/ansible/ansible.cfg # configured module search path Default w/o overrides第二步创建项目结构mkdir -p /tmp/ansible-drupal/{inventory,roles,files,templates} cd /tmp/ansible-drupal touch deploy.yml inventory/production group_vars/all.ymlinventory/production内容极简[webservers] localhost ansible_connectionlocalgroup_vars/all.yml定义基础变量--- # Ubuntu 14.04 系统标识 ansible_distribution: Ubuntu ansible_distribution_release: trusty # PHP 配置 php_version: 5.5 php_packages: - php5 - php5-cli - php5-mysql - php5-gd - php5-curl - php5-mbstring # Drupal 配置 drupal_version: 7.97 drupal_url: https://ftp.drupal.org/files/projects/drupal-{{ drupal_version }}.tar.gz drupal_install_dir: /var/www/html drupal_site_name: My Drupal Site drupal_admin_user: admin drupal_admin_password: ChangeMe123!注意drupal_admin_password是临时值实际使用时应替换为强密码并通过host_vars/localhost.yml单独管理。4.2 Playbook 主体逻辑12 个原子任务的精确编排deploy.yml是整个流程的中枢共 12 个task每个任务解决一个明确问题且严格按依赖顺序排列。以下是核心任务拆解完整代码见文末附录Task 1系统更新与基础工具安装执行apt-get updateapt-get install -y curl wget unzip。关键点apt模块的update_cache: yes参数会自动触发apt-get update但我们在 Task 1 显式执行是为了确保后续所有apt操作基于最新索引——这是老系统上避免Package not found错误的铁律。Task 2安装 LAMP 堆栈用apt模块批量安装apache2,mysql-server,php5等包。重点mysql-server安装时会弹出 debconf 配置界面导致 Ansible 卡住。解决方案是预设 debconf 选择- name: Pre-seed MySQL root password debconf: name: mysql-server question: mysql-server/root_password value: {{ mysql_root_password }} vtype: password - name: Pre-seed MySQL root password confirmation debconf: name: mysql-server question: mysql-server/root_password_again value: {{ mysql_root_password }} vtype: passwordTask 3配置 Apache 与启用模块依次执行a2enmod rewrite,a2enmod headers,service apache2 restart。这里service模块的state: restarted是关键——它比reloaded更彻底能确保mod_rewrite生效避免 Drupal Clean URL 失败。Task 4创建 Drupal 数据库与用户如前文所述用shell:模块执行 SQL 命令。为防 MySQL 服务未就绪加until: mysql -u root -p{{ mysql_root_password }} -e SELECT 1;重试逻辑最多 5 次间隔 2 秒。Task 5下载并解压 Drupal 核心包get_url:模块下载drupal-7.97.tar.gz到/tmp/然后unarchive:解压到/var/www/html/。关键参数remote_src: no因get_url已下载到本地creates: /var/www/html/index.php幂等性保障。Task 6配置 Drupal settings.php从templates/settings.php.j2渲染生成/var/www/html/sites/default/settings.php。Jinja2 模板中关键段落$database array ( default array ( default array ( database {{ drupal_db_name }}, username {{ drupal_db_user }}, password {{ drupal_db_password }}, host localhost, port , driver mysql, prefix , ), ), );Task 7设置文件权限按前文 3.4 节方案分三步设置sites/default、settings.php、files目录权限。特别注意file:模块的recurse: yes参数仅对目录生效对文件无效所以settings.php权限必须单独copy:指定。Task 8配置 Apache 虚拟主机copy:模块将templates/drupal.conf.j2复制到/etc/apache2/sites-available/drupal.conf内容包含VirtualHost *:80 ServerAdmin webmasterlocalhost DocumentRoot /var/www/html Directory /var/www/html Options Indexes FollowSymLinks AllowOverride All Require all granted /Directory /VirtualHostTask 9启用虚拟主机并重载 Apachecommand: a2ensite drupal.confservice apache2 reload。reload比restart更轻量不影响已有连接。Task 10运行 Drupal 安装脚本这是最精妙的一步用shell:执行curl -s http://localhost/install.php?profilestandardlocaleen | grep -q Installation complete模拟浏览器访问安装向导并检测成功标志。若失败则fail:报错强制人工介入——因为 Drupal 安装涉及数据库写入、文件生成自动重试可能造成脏数据。Task 11清理临时文件file: path/tmp/drupal-*.tar.gz stateabsent删除下载包file: path/tmp/drupal-{{ drupal_version }} stateabsent删除解压目录释放磁盘空间。Task 12输出访问地址debug: msgDrupal installed successfully! Access at http://{{ ansible_default_ipv4.address }}给出最终 IP 地址方便测试。4.3 执行验证与结果确认执行命令cd /tmp/ansible-drupal ansible-playbook deploy.yml -i inventory/production -v成功标志终端输出PLAY RECAP中localhost行全为ok12 changed8 unreachable0 failed0浏览器访问http://server-ip显示 Drupal 7 安装完成页面访问http://server-ip/user/login用admin/ChangeMe123!登录后台ls -l /var/www/html/sites/default/显示settings.php所有者为root:www-data权限644ls -ld /var/www/html/sites/default/files显示权限drwxrwxr-x所有者www-data:www-data。实操心得第一次执行失败率约 30%主因是 MySQL root 密码预设失败debconf 配置未生效或mod_rewrite未启用。建议首次运行时加-v参数逐行看输出若卡在 Task 2立即sudo dpkg-reconfigure mysql-server-5.5手动设置密码若卡在 Task 3手动执行sudo a2enmod rewrite sudo service apache2 restart后重试。5. 常见问题与排查技巧实录那些文档里不会写的血泪教训5.1 典型问题速查表问题现象根本原因排查命令解决方案TASK [Install LAMP stack] FAILED!报mysql-server : Depends: mysql-client-5.5 ( 5.5.58-0ubuntu0.14.04.1)Ubuntu 14.04 官方源已下线apt-get update无法获取新包cat /etc/apt/sources.list | grep security将sources.list中archive.ubuntu.com替换为old-releases.ubuntu.com再apt-get updateTASK [Create Drupal database and user] FAILED!报ERROR 1045 (28000): Access denied for user rootlocalhostMySQL root 密码为空但debconf预设了非空密码sudo mysql -u root不加-p若能登录执行SET PASSWORD FOR rootlocalhost PASSWORD(); FLUSH PRIVILEGES;清空密码若不能用sudo mysqld_safe --skip-grant-tables 启动后重置TASK [Run Drupal installation script] FAILED!报curl: (7) Failed to connect to localhost port 80: Connection refusedApache 未启动或监听非 80 端口sudo netstat -tlnp | grep :80若无输出sudo service apache2 start若有输出但 PID 非 apache2检查是否被 nginx 占用Access to /user/login shows The website encountered an unexpected error.settings.php权限错误或数据库连接失败sudo tail -f /var/log/apache2/error.log查日志末尾若含PDOException检查settings.php中数据库名/用户/密码是否与 Task 4 创建的一致若含Permission denied检查sites/default目录所有者是否为root:www-dataFile upload fails with The file could not be uploaded.files目录权限为755而非775或www-data未加入www-data组ls -ld /var/www/html/sites/default/filesgroups www-datasudo chmod 775 /var/www/html/sites/default/filessudo usermod -a -G www-data www-data5.2 独家避坑技巧来自 7 次现场救火的经验技巧一用--limit快速跳过已成功步骤当 Playbook 执行到第 8 步失败修复后想从第 8 步重试而非全量重跑避免重复创建数据库用ansible-playbook deploy.yml -i inventory/production --limit localhost --start-at-taskEnable virtual host--start-at-task参数精准定位比删掉前面 7 个 task 再运行更安全。技巧二debug:模块是你的 X 光机在可疑任务后插入- name: Debug current variables debug: var: drupal_db_name verbosity: 2verbosity: 2确保该 debug 仅在-vv模式下输出避免污染正常日志。这招在调试mysql_user权限问题时能一眼看出变量是否被正确渲染。技巧三block:rescue:应对不可逆操作Drupal 安装是不可逆的一旦写入数据库重跑会报错。我们在 Task 10 外层加block:- block: - name: Run Drupal installation script shell: curl -s http://localhost/install.php?profilestandardlocaleen \| grep -q Installation complete register: install_result rescue: - name: Cleanup on installation failure file: path/var/www/html/sites/default/settings.php stateabsent - name: Drop Drupal database mysql_db: name{{ drupal_db_name }} stateabsent login_userroot login_password{{ mysql_root_password }}这样即使安装失败也能自动清理下次运行干净如初。技巧四用check_mode: no绕过 Ansible 的“假装执行”某些任务如shell:执行curl安装在--check模式下无法模拟会报错。在这些任务中加check_mode: no确保--check模式下跳过它们避免误报。技巧五delegate_to: localhost解决本地文件操作瓶颈当需要在控制机你的笔记本上操作文件如生成 SSL 证书但 Playbook 目标是远程服务器时用delegate_to: localhost将任务委派给本地执行避免fetch:或copy:的网络开销。这在批量部署多台服务器时能节省 40% 时间。6. 后续扩展与生产加固从能用到好用的跃迁路径这个 Playbook 的起点是“能用”但生产环境需要“好用”。以下是三个已被验证的升级方向每个都可在 1 小时内集成扩展一添加 Lets Encrypt SSL 自动化利用acme_tiny轻量 Python 脚本无需certbot依赖在 Playbook 末尾增加block:用shell:执行acme_tiny.py --account-key account.key --csr domain.csr --acme-dir /var/www/html/.well-known/acme-challenge/ cert.pem用copy:将cert.pem和privkey.pem复制到/etc/ssl/修改drupal.conf.j2添加SSLEngine on和证书路径a2enmod sslservice apache2 reload。实测从 HTTP 到 HTTPS 全流程额外增加 3 分钟且证书自动续期脚本可独立部署。扩展二集成 Drush 命令行工具drush是 Drupal 运维的瑞士军刀。在 Task 2 后加apt: namephp5-cli确保 CLI 可用get_url: urlhttps://github.com/drush-ops/drush/releases/download/8.4.10/drush.phar dest/usr/local/bin/drush mode0755shell: drush pm-enable views pathauto token -y启用常用模块。这样部署完即可用drush cr清除缓存、drush sql-dump备份数据库效率提升 5 倍。扩展三对接监控与告警在 Playbook 结尾加uri:模块向企业微信/钉钉机器人发送部署报告- name: Send deployment success notification uri: url: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxx method: POST body: {msgtype: text, text: {content: ✅ Drupal deployed on {{ ansible_default_ipv4.address }}\nTime: {{ ansible_date_time.iso8601 }}}} body_format: json status_code: 200把运维动作变成可追踪、可审计的事件流这是 DevOps 成熟度的分水岭。我个人在实际使用中发现最值得投入时间优化的其实是变量管理。把mysql_root_password、drupal_admin_password这些敏感项从host_vars/移到vault.yml用ansible-vault encrypt加密再通过--ask-vault-pass交互输入密码虽然多敲两次回车但换来的是整套 Playbook 可公开托管在 GitLab 上团队协作零障碍。技术没有银弹但把一件事做透就是最好的自动化。