几年前,我初创的 网站设计公司 开始成型;然而,我们遇到一个问题:管理客户的 Web 服务器和代码部署。我们无法建立一个简化的服务器配置和维护操作系统安全补丁的过程。我们已经掌握了开发周期,但服务器管理成为了我们工作中的难题。我们还需要根据网站的特定需求严格控制每个服务器。此外,共享主机**不是**长期的解决方案。
我开始寻找可以解决此问题的预构建解决方案,但没有取得特别的成功。最初,我手动配置服务器。这个过程很快证明既单调乏味又容易出错。我最终学习了 Ansible,并创建了一个由自定义 Ansible 角色、Bash 脚本和 Ansible Galaxy 角色组成的自制集合,进一步简化了流程——但同样,在服务器达到 100% 之前,仍然需要执行许多手动步骤。
我不是服务器专家(我也不会假装自己是),在这一点上,很明显走这条路从长远来看不会有好结果。我正在接手新的客户,需要一个解决方案,否则我会危及我们的可持续发展能力,更不用说增长了。我花费大量时间在 shell 中输入任意的 sudo apt-get update
命令,而我本应该管理客户或编写代码。更不用说我还处理底层操作系统的持续安全更新及其应用程序了。
告诉我,这些听起来是否熟悉?
令人欣慰的是,就在这时,Roots 团队发布了用于服务器配置的 Trellis;在测试之后,事情似乎就绪了。一个额外的好处是,Trellis 还处理复杂的代码部署,这恰好是我需要的,因为我们构建的大多数客户网站和 Web 应用程序都具有一个相对复杂的 WordPress 构建过程,使用 Composer、npm、webpack 等。更好的是,只需几分钟即可启动一个新项目。在花费数百小时完善我的 Trellis 配置过程之后,我希望将我学到的东西传授给你们,并为你们节省我所花费的无数小时的研究、试验和手动工作。
关于 Bedrock 的说明
我们将假设您的 WordPress 项目使用 Bedrock 作为其基础。Bedrock 由维护 Trellis 的同一团队维护,是一个“具有现代开发工具、更简单的配置和改进的文件夹结构的 WordPress 样板”。这篇文章没有明确解释如何管理 Bedrock,但它非常易于设置,您可以在其 文档 中阅读相关内容。Trellis 本身设计用于部署 Bedrock 项目。
关于 WordPress 网站存储库中应该包含哪些内容的说明
这个项目教会我的其中一件事是,WordPress 应用程序通常只是主题(或在父/子主题关系中的子主题)。其他所有内容,包括插件、库、父主题甚至 WordPress 本身,都只是依赖项。**这意味着我们的版本控制系统通常应该只包含主题,我们可以使用 Composer 来管理所有依赖项。**简而言之,在其他地方管理的任何代码都不应该进行版本控制。我们应该有一种方法让 Composer 在部署过程中将其拉取进来。Trellis 为我们提供了一种简单直接的方法来实现这一点。
开始
以下是一些我接下来假设的内容
- 新网站的代码位于
~/Sites/newsite
目录中 - 暂存 URL 为
https://newsite.statenweb.com
- 生产 URL 为
https://newsite.com
- Bedrock 作为您的 WordPress 应用程序的基础
- Git 用于版本控制,GitHub 用于存储代码。该网站的存储库为:
[email protected]:statenweb/newsite.git
我的本地开发环境有点老派,所以我放弃了 Vagrant,转而使用 MAMP 进行本地开发。我们不会在这篇文章中介绍如何设置本地环境。
我设置了一个 用于 macOS 的快速启动 Bash 脚本,以进一步自动化此过程。
我们需要使用两个主要项目:Trellis 和 Bedrock。如果您还没有这样做,请为该网站创建一个目录(mkdir ~/Sites/newsite
)并从那里克隆这两个项目。我将 Trellis 克隆到 /trellis
目录,并将 Bedrock 克隆到 /site
目录
cd ~/Sites/newsite
git clone [email protected]:roots/trellis.git
git clone [email protected]:roots/bedrock.git site
cd trellis
rm -rf .git
cd ../site
rm -rf .git
最后四行使我们能够正确地进行版本控制。当您对项目进行版本控制时,存储库应该包含 ~/Sites/newsite
中的所有内容。
现在,进入 trellis
并进行以下更改
首先,打开 ~/Sites/newsite/trellis/ansible.cfg
并将以下行添加到 [defaults]
键的底部
vault_password_file = .vault_pass
host_key_checking = False
第一行允许我们使用 .vault_pass
文件加密所有 vault.yml
文件,这些文件将存储我们的密码、敏感数据和盐。
第二个 host_key_checking = False
可以为了安全起见省略,因为它可能被认为有些危险。也就是说,它仍然很有帮助,因为我们不必管理主机密钥检查(即,在提示时键入 yes
)。
Ansible 密钥库密码

接下来,让我们创建文件 ~/Sites/newsite/trellis/.vault_pass
,并在其中输入一个 64 个字符的随机哈希值。我们可以使用哈希生成器来创建它(例如,请参见此处)。此文件在默认的 .gitignore
中被明确忽略,因此它不会(或不应该)上传到源代码控制。我将此密码保存在一个极其安全的地方。请务必运行 chmod 600 .vault_pass
以限制对该文件的访问。
这样做的原因是,我们可以将加密的密码存储在版本控制系统中,而不必担心泄露任何服务器的机密信息。需要特别说明的是,.vault_pass
文件未(也不应该)提交到存储库,并且 vault.yml
文件已正确加密;关于这方面的内容,请参见下面的“加密秘密变量”部分。
设置目标主机

接下来,我们需要设置我们的目标主机。目标主机是 Trellis 将部署我们代码的 Web 地址。在本教程中,我们将配置 newsite.com
作为我们的生产目标主机,并将 newsite.statenweb.com
作为我们的暂存目标主机。为此,让我们首先更新存储在 ~/Sites/newsite/trellis/hosts/production
中的 production
主机文件中的生产服务器地址,如下所示:
[production]
newsite.com
[web]
newsite.com
接下来,我们可以更新存储在 ~/Sites/newsite/trellis/hosts/staging
中的 staging
主机文件中的暂存服务器地址,如下所示:
[staging]
newsite.statenweb.com
[web]
newsite.statenweb.com
设置 GitHub SSH 密钥
为了使部署成功,SSH 密钥需要正常工作。Trellis 利用 GitHub 如何公开所有公钥(SSH 密钥),因此您无需手动添加密钥。要进行设置,请进入 group_vars/all/users.yml
并更新 web_user
和 admin_user
对象的 keys
值,以包含您的 GitHub 用户名。例如:
users:
- name: '{{ web_user }}'
groups:
- '{{ web_group }}'
keys:
- https://github.com/matgargano.keys
- name: '{{ admin_user }}'
groups:
- sudo
keys:
- https://github.com/matgargano.keys
当然,所有这一切都假设您拥有一个 GitHub 帐户,并且所有必要的公钥都与之关联。
网站元数据
我们将重要的网站信息存储在
~/Sites/newsite/trellis/group_vars/production/wordpress_sites.yml
中(用于生产环境)~/Sites/newsite/trellis/group_vars/staging/wordpress_sites.yml
中(用于暂存环境)。
让我们更新暂存 wordpress_sites.yml
中的以下信息:
wordpress_sites:
newsite.statenweb.com:
site_hosts:
- canonical: newsite.statenweb.com
local_path: ../site
repo: [email protected]:statenweb/newsite.git
repo_subtree_path: site
branch: staging
multisite:
enabled: false
ssl:
enabled: true
provider: letsencrypt
cache:
enabled: false
此文件表示我们
- 删除了网站主机重定向,因为暂存环境不需要它们
- 为站点密钥(`newsite.statenweb.com`)设置规范站点URL(`newsite.statenweb.com`)。
- 定义了存储库的URL。
- 部署到此目标的Git仓库分支为`staging`,即我们使用名为`staging`的单独分支作为我们的预发布站点。
- 启用了SSL(设置为true),这将在服务器配置时安装SSL证书。
让我们更新生产环境的`wordpress_sites.yml`中的以下信息。
wordpress_sites:
newsite.com:
site_hosts:
- canonical: newsite.com
redirects:
- www.newsite.com
local_path: ../site # path targeting local Bedrock site directory (relative to Ansible root)
repo: [email protected]:statenweb/newsite.git
repo_subtree_path: site
branch: master
multisite:
enabled: false
ssl:
enabled: true
provider: letsencrypt
cache:
enabled: false
同样,这翻译成我们:
- 为站点密钥(`newsite.com`)设置规范站点URL(`newsite.com`)。
- 为`www.newsite.com`设置重定向。
- 定义了存储库的URL。
- 部署到此目标的Git仓库分支为`master`,即我们使用名为`master`的单独分支作为我们的生产站点。
- 启用了SSL(设置为true),这将在你配置服务器时安装SSL证书。
在`wordpress_sites.yml`中,你可以进一步配置服务器的缓存,这超出了本指南的范围。有关更多信息,请参阅Trellis关于FastCGI缓存的文档。
秘密变量

我们的预发布和生产环境都将包含一些秘密信息,包括root用户密码、MySQL root密码、站点密钥以及更多内容。如前所述,Ansible Vault和使用`.vault_pass`文件使这个过程变得轻而易举。
我们将这些秘密站点信息存储在以下位置:
- 生产环境:`~/Sites/newsite/trellis/group_vars/production/vault.yml`
- 预发布环境:`~/Sites/newsite/trellis/group_vars/staging/vault.yml`
让我们更新预发布环境的`vault.yml`中的以下信息。
vault_mysql_root_password: pK3ygadfPHcLCAVHWMX
vault_users:
- name: "{{ admin_user }}"
password: QvtZ7tdasdfzUmJxWr8DCs
salt: "heFijJasdfQbN8bA3A"
vault_wordpress_sites:
newsite.statenweb.com:
env:
auth_key: "Ab$YTlX%:Qt8ij/99LUadfl1:U]m0ds@N<3@x0LHawBsO$(gdrJQm]@alkr@/sUo.O"
secure_auth_key: "+>Pbsd:|aiadf50;1Gz;.Z{nt%Qvx.5m0]4n:L:h9AaexLR{1B6.HeMH[w4$>H_"
logged_in_key: "c3]7HixBkSC%}-fadsfK0yq{HF)D#1S@Rsa`i5aW^jW+W`8`e=&PABU(s&JH5oPE"
nonce_key: "5$vig.yGqWl3G-.^yXD5.ddf/BsHx|i]>h=mSy;99ex*Saj<@lh;3)85D;#|RC="
auth_salt: "Wv)[t.xcPsA}&/]rhxldafM;h(FSmvR]+D9gN9c6{*hFiZ{]{,#b%4Um.QzAW+aLz"
secure_auth_salt: "e4dz}_x)DDg(si/8Ye&U.p@pB}NzHdfQccJSAh;?W)>JZ=8:,i?;j$bwSG)L!JIG"
logged_in_salt: "DET>c?m1uMAt%hj3`8%_emsz}EDM7R@44c0HpAK(pSnRuzJ*WTQzWnCFTcp;,:44"
nonce_salt: "oHB]MD%RBla*#x>[UhoE{hm{7j#0MaRA#fdQcdfKe]Y#M0kQ0F/0xe{cb|g,h.-m"
现在,让我们更新生产环境的`vault.yml`中的以下信息。
vault_mysql_root_password: nzUMN4zBoMZXJDJis3WC
vault_users:
- name: "{{ admin_user }}"
password: tFxea6ULFM8CBejagwiU
salt: "9LgzE8phVmNdrdtMDdvR"
vault_wordpress_sites:
newsite.com:
env:
db_password: eFKYefM4hafxCFy3cash
# Generate your keys here: https://roots.io/salts.html
auth_key: "|4xA-:Pa=-rT]&!-(%*uKAcdix_Uv,`/(7dk1+;b|ql]42gh&HPFdDZ@&of"
secure_auth_key: "171KFFX1ztl+1I/P$bJrxi*s;}.>S:{^-=@*2LN9UfalAFX2Nx1/Q&i&LIrI(BQ["
logged_in_key: "5)F+gFFe}}0;2G:k/S>CI2M*rjCD-mFX?Pw!1o.@>;?85JGu}#(0#)^l}&/W;K&D"
nonce_key: "5/[Zf[yXFFgsc#`4r[kGgduxVfbn::<+F<$jw!WX,lAi41#D-Dsaho@PVUe=8@iH"
auth_salt: "388p$c=GFFq&hw6zj+T(rJro|V@S2To&dD|Q9J`wqdWM&j8.KN]y?WZZj$T-PTBa"
secure_auth_salt: "%Rp09[iM0.n[ozB(t;0vk55QDFuMp1-=+F=f%/Xv&7`_oPur1ma%TytFFy[RTI,j"
logged_in_salt: "dOcGR-m:%4NpEeSj>?A8%x50(d0=[cvV!2x`.vB|^#G!_D-4Q>.+1K!6FFw8Da7G"
nonce_salt: "rRIHVyNKD{LQb$uOhZLhz5QX}P)QUUo!Yw]+@!u7WB:INFFYI|Ta5@G,j(-]F.@4"
两者都需要以下关键行:
- 站点密钥必须与我们正在使用的`wordpress_sites.yml`中的密钥匹配,我们使用`newsite.statenweb.com:`作为预发布环境的密钥,`newsite.com:`作为生产环境的密钥。
- 我随机生成了`vault_mysql_root_password`、`password`、`salt`、`db_password`和`db_password`。我使用了Roots的助手来生成密钥。
我通常使用Gmail的SMTP服务器,并使用Post SMTP插件,因此我无需编辑`~/Sites/newsite/group_vars/all/vault.yml`。
加密秘密变量

如前所述,我们使用Ansible Vault来加密我们的`vault.yml`文件。以下是如何加密文件并使其准备好存储在我们的版本控制系统中的步骤。
cd ~/Sites/newsite/trellis
ansible-vault encrypt group_vars/staging/vault.yml group_vars/production/vault.yml
现在,如果我们打开`~/Sites/newsite/trellis/group_vars/staging/vault.yml`或`~/Sites/newsite/trellis/group_vars/production/vault.yml`,我们只会看到乱码文本。这可以安全地存储在仓库中,因为唯一解密它的方法是使用`.vault_pass`。不用说,要确保`.vault_pass`本身不会提交到仓库中。
关于编译、转译等的说明
另一件超出范围的事情是设置Trellis部署以处理使用构建工具(如npm和webpack)的构建过程。这是一个处理自定义构建的示例代码,可以包含在`~/Sites/newsite/trellis/deploy-hooks/build-before.yml`中。
---
-
args:
chdir: "{{ project.local_path }}/web/app/themes/newsite"
command: "npm install"
connection: local
name: "Run npm install"
-
args:
chdir: "{{ project.local_path }}/web/app/themes/newsite"
command: "npm run build"
connection: local
name: "Compile assets for production"
-
name: "Copy Assets"
synchronize:
dest: "{{ deploy_helper.new_release_path }}/web/app/themes/newsite/dist/"
group: no
owner: no
rsync_opts: "--chmod=Du=rwx,--chmod=Dg=rx,--chmod=Do=rx,--chmod=Fu=rw,--chmod=Fg=r,--chmod=Fo=r"
src: "{{ project.local_path }}/web/app/themes/newsite/dist/"
这些指令构建了资源并将其移动到我明确决定不进行版本控制的目录中。我希望撰写一篇后续指南,专门探讨这个问题。
配置

我不会详细介绍服务器本身的设置,但我通常会进入DigitalOcean并启动一个新的Droplet。截至撰写本文时,Trellis是在Ubuntu 18.04 LTS (Bionic Beaver)上编写的,它充当生产服务器。在该Droplet中,我将添加一个公钥,该公钥也包含在我的GitHub帐户中。为简单起见,我可以使用与预发布服务器相同的服务器。这种情况可能不是你正在使用的;也许你使用一台服务器来托管所有预发布站点。如果是这种情况,你可能需要关注`~/Sites/newsite/trellis/group_vars/staging/vault.yml`中配置的密码。
在DNS级别,我将newsite.com的裸A记录映射到新创建的Droplet的IP地址。然后,我将CNAME www映射到@。此外,newsite.statenweb.com的A记录将映射到Droplet的IP地址(或者,可以为newsite.statenweb.com创建指向newsite.com的CNAME记录,因为在本例中它们都在同一台服务器上)。
DNS传播后,这可能需要一些时间,可以通过运行以下命令配置预发布服务器。
首先,你可能需要在任何其他操作之前运行此命令:
ansible-galaxy install -r requirements.yml
然后,在继续之前安装所需的Ansible Galaxy角色:
cd ~/Sites/newsite/trellis
ansible-playbook server.yml -e env=staging
接下来,配置生产服务器:
cd ~/Sites/newsite/trellis
ansible-playbook server.yml -e env=production
部署
如果所有设置都正确,可以部署到预发布环境,我们可以运行以下命令:
cd ~/Sites/newsite/trellis
ansible-playbook deploy.yml -e "site=newsite.statenweb.com env=staging" -i hosts/staging
完成后,访问`https://newsite.statenweb.com`。这应该会显示WordPress安装提示,其中提供了完成站点设置的后续步骤。
如果预发布环境一切正常,我们可以发出以下命令来部署到生产环境:
cd ~/Sites/newsite/trellis
ansible-playbook deploy.yml -e "site=newsite.com env=production" -i hosts/production
与预发布环境类似,访问`https://newsite.com`后也应该会提示安装步骤以完成部署。
开始部署!
希望这可以解决我个人曾经遇到的问题,并帮助你节省大量时间和避免不必要的麻烦。拥有稳定、安全且可扩展的服务器环境,这些环境只需很少的精力即可启动,这极大地改变了我们团队的工作方式以及我们满足客户需求的能力。
虽然从技术上讲我们已经完成了,但仍然需要采取一些进一步的步骤来全面完善你的环境。
- 将插件、库和父主题等依赖项添加到`~/Sites/newsite/composer.json`中,并运行`composer update`以获取最新的清单版本。
- 将主题放置到`~/Sites/newsite/site/themes/`中。(请注意,可以使用任何WordPress主题)。
- 在其中一个部署钩子中包含你需要的任何构建过程(例如,转译ES6、编译SCSS等)。(请参阅Trellis钩子的文档)。
我还能够深入研究企业级持续集成和持续交付,以及如何通过运行自定义Composer服务器处理高级插件以及其他操作,而无需产生任何额外费用。希望在未来的文章中能够涉及这些方面。
Trellis提供了一种非常简单的配置WordPress服务器的方法。感谢Trellis,手动创建、修补和维护服务器的日子一去不复返了!
这看起来太棒了,Matt!感谢你的信息。
不错的文章!我强烈建议学习使用Vagrant。它更容易镜像你的服务器配置。在本地使用Vagrant启动几个新服务器后,它会变得越来越容易,并且与使用Trellis/Ansible部署完美契合。Vagrant也出现在其他框架中,比如Laravel。
谢谢,Nick!
我过去曾使用过Vagrant,它很棒。但对于每个环境都使用一个完整的Linux系统,很快就会变得非常占用磁盘空间(磁盘空间方面)。
最直接的替代方案是为什么不使用 Docker 呢?说实话,我过去用过 Docker,也尝试过 Docker Compose,并构建了自己的容器应用程序——我将其用于项目的 CI/CD,但 MAMP 的简洁性对我来说是阻力最小的路径——一旦你克服了“呃,你用 MAMP——真是个菜鸟”的无稽之谈。
感谢你的评论!
为什么不使用托管服务,让他们处理所有更新,将数据库放在远程/外部服务器上,并将镜像放在 AWS 上呢?
这绝对是一种方法……正如人们常说的,烘焙蛋糕的方法不止一种……!
这真是太棒了。我尝试构建具有此功能的东西已经好几年了。我正在从了解 Sage/Roots 反向工作,但我理解这个过程。我始终无法完全弄清楚 Trellis 部署部分。感谢您分享!
很高兴它对您有所帮助!