十七、托管、资源调配和部署

托管、资源调配和部署是公认的三个非常不同的活动,通常与整个应用程序生命周期管理同时进行。然而,某些类型的托管解决方案几乎不可能实现无缝部署,而另一些类型的托管解决方案则使开发人员最终体验到愉悦和时效。这就把我们带到了最重要的一点,那就是,为什么开发人员还要为这些系统操作的事情操心呢?对于这个问题有很多答案。然而,真正的推销很简单:市场需要它。如今,开发人员陷入了一个多学科活动的网络中,这往往超出了编码技能本身,并在某种程度上进入了系统操作。不是我的工作这句咒语在这里几乎是留给我们的,这最终是可以接受的,因为对支持整个应用程序生命周期的活动有很强的了解可以让我们在面对可能的停机时更快速地响应。

在本章中,我们将通过以下几个部分对其中一些活动进行高级别概述:

  • 选择正确的托管计划
  • 自动化资源调配
  • 自动化部署
  • 连续积分

选择正确的托管计划

为我们的下一个项目选择正确的托管计划可能是一个乏味的挑战。有许多类型的解决方案可供选择,其中一些包括:

  • 共享服务器
  • 虚拟专用服务器
  • 专用服务器
  • 帕斯

他们都有自己的优点缺点。然而,一旦决定因素被诸如内存、CPU、带宽和磁盘存储等功能所主导,这些年来就变得越来越便宜。如今,自动扩展部署方便成为了一个同样重要的指标。虽然最终定价起着至关重要的作用,但许多现代托管解决方案都可以以合理的价格提供。

共享服务器

共享 web 托管服务是许多不同用户托管其应用程序的服务。托管提供商通常提供一个经过良好调优的 web 服务器,该服务器具有 MySQL 或 PostgreSQL 数据库和 FTP 访问权限。除此之外,通常还有一个基于 web 的控制面板系统,如 cPanel、Plesk、H-Sphere 等。这使我们能够通过一个漂亮的图形界面,直接从浏览器管理有限的一组功能。

流行的 PC 杂志(http://www.pcmag.com 分享 2017 年最佳网络托管服务名单如下:

每个 web 托管服务似乎都提供了一组类似的功能,如以下屏幕截图所示:

虽然负担得起的共享服务器价格似乎很诱人,但对服务器缺乏控制限制了它在任何严肃应用程序中的使用。我们的应用程序与十几个、一百个甚至一千个其他应用程序共享相同的 CPU、内存和存储。我们无法安装任何我们想要的软件,如果我们的应用程序需要一些花哨的 PHP 扩展,这甚至可能成为交易的破坏者,这就是为什么这种穷人的托管是我们应该全心全意避免的,除了名片或博客类型的应用程序。

虚拟专用服务器

虚拟专用服务器VPS为托管提供商提供的虚拟机。这台机器然后运行它自己的操作系统,我们通常有一个完全的超级用户访问。VPS 本身与其他 VPS 机器共享同一组物理硬件资源。这意味着我们的 VPS 性能很容易受到其他 VPS 机器进程的影响。

流行的 PCMag 杂志(http://www.pcmag.com 分享 2017 年度最佳 VPS 网络托管服务名单如下:

这些托管服务之间有很多不同,主要是在内存和存储方面,如您在以下屏幕截图中所示:

虽然 VPS 仍然是一种共享资源的形式,但它允许我们比传统的共享主机拥有更大的自由度。拥有超级用户对机器的完全访问权意味着我们可以安装几乎任何我们想要的软件。这也意味着我们要承担更大程度的责任。

专用服务器

专用服务器采用托管提供商提供的真实物理机器。这样的机器不会与我们以外的任何人共享资源。这使它成为高性能和任务关键型应用程序的可行选择。

流行的 PCMag 杂志(http://www.pcmag.com 分享 2017 年度最佳专用网络托管服务名单如下:

这些托管服务之间有很多不同,主要是在内存和存储方面,正如您在本屏幕截图中所看到的:

虽然它们的成本较高,但专用服务器保证了一定水平的性能和对机器的完全控制。同时,管理可伸缩性和冗余也很容易成为一项挑战。

帕斯

平台即服务PaaS)是一种特殊类型的主机,提供商在其中提供加速应用程序开发所需的硬件和软件工具。我们甚至可以将 PaaS 与由数十种易于连接的服务支持的专用服务器的功能和灵活性进行比较,这些服务有助于提高可用性、可靠性、可扩展性,和应用程序开发活动。这使得它成为开发人员的一个流行选择。

热门的IT 中心站站点(https://www.itcentralstation.com 分享 2017 年最佳 PaaS 云供应商名单如下:

2017 年 4 月的报告如下:

尽管所有这些服务都有很多可提供的,但值得指出的是,2016 年 Gartner 将 Amazon AWS 命名为云基础设施幻方图服务,该服务具有最完整的愿景。评估标准基于几个关键因素:

  • 市场理解
  • 营销策略
  • 销售策略
  • 提供(产品)策略
  • 商业模式
  • 垂直/行业战略
  • 创造
  • 地理战略

Amazon AWS 的伟大起点是其 EC2 服务,该服务提供可调整大小的虚拟服务器。这些服务器的行为与专用服务器非常相似,但在云中,我们可以选择要部署这些服务器的世界区域。除此之外,Amazon AWS 产品中还有数十项其他服务可以丰富整个应用程序管理:

一个易于使用的界面、丰富的服务、合理的价格、优秀的文档、认证和可用的工具是开发者在亚马逊 AWS 上的一些卖点

自动化资源调配

资源调配这个术语最近在开发人员中得到了广泛的关注。它是指使用所需的每一位软件设置和配置服务器的活动,使其为应用程序使用做好准备。虽然这听起来很像系统操作类型的工作,但随着云服务及其相关工具的兴起,开发人员发现这很有趣。

从历史上看,资源调配意味着大量手动类型的工作。通用自动资源调配工具不如今天那么多。这意味着有时资源调配需要几天甚至几周的时间。从当今市场需求的角度来看,这样的场景很难想象。如今,一个应用程序通常由多个不同的服务器提供服务,每个服务器都针对一个功能,例如 web(Apache、Nginx 等)、存储(MySQL、Redis 等)、会话(Redis、Memcached 等)、静态内容(Nginx 等),等等。我们根本负担不起花上几天的时间安装每台服务器。

我们可以使用几种流行的工具来自动化资源调配,其中一些工具包括以下四种:

与其他同类工具一样,这些工具的构建目标都是使配置和维护数十台、数百台甚至数千台服务器变得更加容易。虽然所有这些工具都更有可能以同样的效果完成任何资源调配工作,但让我们仔细看看其中的一个。2012 年发布的Ansible是四款中最年轻的一款。它是一个开源工具,可以自动化软件配置、配置管理和应用程序部署。此工具通过 SSH 执行其所有功能,无需在目标节点/服务器上安装任何代理软件。这本身就使得它成为开发者的一个有利选择。

Ansible 有几个关键概念,其中一些概念如下:

  • 库存:这是 Ansible 托管服务器的列表
  • 剧本:这是 Ansible 以 YAML 格式表示的配置
  • 角色:这是围绕基于文件结构的 include 指令的自动化
  • 任务:这是 Ansible 可以执行的可能操作

The https://galaxy.ansible.com service acts as a hub that provides ready-to-use roles.

为了对 Ansible 有一个非常基本的了解,让我们根据以下内容进行一个非常简单和快速的演示:

  • Ubuntu 工作站
  • Ubuntu 服务器

我们将使用ansible工具在服务器机器上提供软件。

设置工作站计算机

使用 Ubuntu 支持的工作站,我们只需运行以下一组命令即可轻松安装 Ansible 工具:

sudo apt-get install software-properties-common
sudo apt-add-repository ppa:ansible/ansible
sudo apt-get update
sudo apt-get install ansible

如果一切顺利,ansible --version应该会给我们一个类似以下截图的输出:

Ansible 是一个用于运行特殊任务的控制台工具。而 ad-hoc 意味着我们可以快速完成某些事情,而不必为此编写整个剧本。

同样,ansible-galaxy --version应该给我们一个类似于以下截图的输出:

ansible-galaxy是一个控制台工具,我们可以使用它来安装、创建和删除角色,或者在 Galaxy 网站上执行任务。默认情况下,该工具使用服务器地址与 Galaxy 网站 API 进行通信 https://galaxy.ansible.com

另外,ansible-playbook --version应该给我们一个类似于以下截图的输出:

ansible-playbook是用于配置管理和部署的控制台工具。

有了 Ansible 工具,让我们确保我们的工作站有一个正确的 SSH 密钥,稍后我们将使用它连接到服务器机器。我们只需运行以下命令,然后在要求输入文件和密码短语时点击Enter键,就可以轻松生成 SSH 密钥:

ssh-keygen -t rsa

这将为我们提供与以下内容非常相似的输出:

使用 Ansible 剧本,我们可以以易读的 YAML 格式定义各种资源调配步骤。

设置服务器计算机

我们之前提到过,有几种托管解决方案允许完全控制服务器机器。这些解决方案以 VPS、专用和云服务的形式出现。在本例中,我们将使用Vultr 云计算VC2),可从获得 https://www.vultr.com 。在不深入了解 Vultr 服务的细节的情况下,可以说,它通过一个易于使用的管理界面提供了一个价格合理的云计算服务。

假设我们已经创建了一个 Vultr 帐户,我们现在要做的第一件事就是向它添加工作站 SSH 公钥。我们可以通过 Vultr 的服务器| SSH 密钥接口轻松实现这一点:

保存 SSH 密钥后,我们可以返回服务器屏幕并单击部署新服务器按钮。这就引出了部署新实例接口,它向我们介绍了几个步骤。我们关注的步骤是服务器类型和 SSH 密钥。

对于服务器类型,让我们继续选择 Ubuntu 16.04 x64:

对于 SSH 密钥,让我们继续选择刚才添加到 Vultr 的 SSH 密钥:

选择这两个选项后,我们可以单击 DeployNow 按钮,这将触发服务器机器的部署。

在这一点上,我们可能想知道,这个练习的目的可能是什么,因为我们已经非常清楚地手动创建了一台服务器机器。毕竟,Ansible 有一个模块来管理 Vultr 上的服务器,所以我们可以轻松地将其用于服务器创建。然而,这里的练习是关于理解将 Ansible“挂接”到现有服务器机器,然后使用它为其提供进一步软件是多么容易的基础。现在我们有了一台正在运行的服务器,让我们继续在工作站上进一步配置 Ansible。

配置 Ansible

回到我们的工作站机器上,让我们继续创建一个项目目录:

mkdir mphp7
cd mphp7/

现在,让我们继续创建一个ansible.cfg文件,其内容如下:

[defaults]
hostfile = hosts

接下来,我们继续创建hosts文件,其内容如下:

[mphp7]
45.76.88.214 ansible_ssh_user=root

在前面的代码行中,45.76.88.214是我们服务器机器的 IP 地址。

我们现在应该能够按如下方式运行ansible工具:

ansible mphp7 -m ping

理想情况下,这将为我们提供以下输出:

如果我们的服务器上缺少 Python 安装,ansible工具可能会抛出模块故障消息:

如果发生这种情况,我们应该使用 SSH 连接到服务器机器中,并按照如下方式安装 Python:

sudo apt-get -y install python

此时,我们的工作站ansible工具应设置为与我们的服务器机器有清晰的通信。

现在,让我们继续快速查找 Galaxy hub 上的 LAMP 服务器角色:

单击其中一个结果可为我们提供有关如何安装它的信息:

通过在工作站上运行以下命令,我们安装了现有的fvarovillodres.lamp角色:

ansible-galaxy install fvarovillodres.lamp

设置 web 服务器

有了新推出的fvarovillodres.lamp规则,我们应该能够毫不费力地部署新的 web 服务器。要做到这一点,只需创建一个剧本,如lamp.yaml,其内容如下:

- hosts: mphp7
 roles:
 - role: fvarovillodres.lamp
 become: yes

现在,我们可以通过以下命令轻松运行我们的lamp.yaml剧本:

ansible-playbook lamp.yml

这将触发fvarovillodres.lamp角色中的任务,我们在完成后从 Galaxy hub 中提取这些任务:

最后,打开一个http://45.76.88.214/URL 应该会给我们一个 Apache 页面。

资源调配,甚至 Ansible 的总体主题是一个值得一本书去研究的广泛主题。这里给出的示例只是为了展示可用工具的易用性,以便以自动化的方式解决资源调配问题。这里有一个重要的收获,那就是我们需要完全控制服务器/节点,以便利用资源调配。这就是为什么共享类型的主机被排除在任何此类讨论之外。

这里给出的确切示例使用单个服务器盒。然而,不难想象,只要修改 Ansible 配置,这种方法就可以轻松地扩展到十几台甚至数百台服务器。我们可以很容易地使用 Ansible 本身来自动化应用程序的部署,例如,每次部署都可能触发一个新的服务器创建过程,代码从某个 Git 存储库或类似的库中提取。但是,有更简单、更专门的工具来处理自动化部署。

自动化部署

部署 PHP 应用程序主要意味着部署 PHP 代码。由于 PHP 是一种解释语言而不是编译语言,PHP 应用程序按原样在源文件中部署其代码。这意味着在部署应用程序时不涉及真正的构建过程,这进一步意味着应用程序部署可以像在服务器 web 目录中执行git pull一样简单。当然,事情从来没有这么简单,因为在部署代码时,我们通常需要安装各种其他位,例如数据库、装入的驱动器、共享文件、权限、连接到服务器的其他服务等等。

我们可以很容易地想象,必须同时将代码从一个 git 存储库手动部署到某个负载均衡器后面的几十个 web 服务器上,这会有多么复杂。这种手动部署肯定会带来负面影响,因为我们最终会在总体部署之间留出一段时间,其中一台服务器可能有较新版本的应用程序代码,而其他服务器仍为旧应用程序提供服务。因此,缺乏一致性只是需要担心的影响性挑战之一。

幸运的是,有十几种工具可以解决自动化部署的挑战。虽然我们不会具体讨论其中任何一个的细节,但为了快速比较,让我们只提及以下两个:

与 AWS CodeDeploy 不同,Deployer 工具与服务无关。也就是说,我们可以使用它将代码部署到我们可以控制的任何服务器上,包括 AWS EC2 实例。另一方面,AWS CodeDeploy 是一个紧密集成到 AWS 本身的服务,这意味着我们不能在 AWS 之外使用它。在这种情况下,这并不意味着 Deployer 比 AWS CodeDeploy 更好。这只是为了说明一些云和 PaaS 服务为自动化部署提供了自己的集成解决方案。

向前看,让我们快速看看我们可以容易地将部署器部署到服务器机器上。

安装展开器

通过以下几个命令安装 Deployer 非常简单:

curl -LO https://deployer.org/releases/v4.3.0/deployer.phar
mv deployer.phar /usr/local/bin/dep 
chmod +x /usr/local/bin/dep

现在,运行dep控制台命令将提供以下输出:

使用部署器

由部署器应用程序组成的几个关键概念包括:

  • 配置:使用set()get()功能,我们设置并获取一个或多个配置选项:
set('color', 'Yellow');
set('hello', function () {
  return run(...)->toString();
});
  • 任务:这些是通过task()功能定义的工作单元,与设置任务描述的desc()方法一起使用。任务内通常有一个或多个功能,如run()
desc('Foggyline task #1');
task('update', 'apt-get update');

desc('Foggyline task #2');
task('task_2', function () {
  run(...);
});
  • 服务器:这是通过 server()函数定义的服务器列表,如下代码段所示:
server('mphp7_staging', 'mphp7.staging.foggyline.net')
 ->user('user')
 ->password('pass')
 ->set('deploy_path', '/home/www')
 ->set('branch', 'stage')
 ->stage('staging');

server('mphp7_prod', 'mphp7.foggyline.net')
 ->user('user')
 ->identityFile()
 ->set('deploy_path', '/home/www')
 ->set('branch', 'master')
 ->stage('production');
  • 流程:表示一组任务,普通型项目使用默认流程,如下所示:
task('deploy', [
    'deploy:prepare',
    'deploy:lock',
    'deploy:release',
    'deploy:update_code',
    'deploy:shared',
    'deploy:writable',
    'deploy:vendors',
    'deploy:clear_paths',
    'deploy:symlink',
    'deploy:unlock',
    'cleanup',
    'success'
]);

通过更改自动生成的deploy.php文件中的流,我们可以轻松创建自己的流。

  • 功能:这是一组提供有用功能的实用功能,如run()upload()ask()等。

使用 Deployer 工具非常简单。除非我们已经有一些以前创建的配方,否则只需运行以下控制台命令即可创建一个配方:

dep init

这启动了一个交互过程,要求我们选择正在处理的项目类型。让我们继续从开始部署我们的 MPHP7-CH16 应用程序 https://github.com/ajzele/MPHP7-CH16 存储库,将其标记为[0]公共:

此命令生成deploy.php文件,其内容如下:

<?php
namespace Deployer;
require 'recipe/common.php';

// Configuration

set('ssh_type', 'native');
set('ssh_multiplexing', true);

set('repository', 'git@domain.com:username/repository.git');
set('shared_files', []);
set('shared_dirs', []);
set('writable_dirs', []);

// Servers

server('production', 'domain.com')
    ->user('username')
    ->identityFile()
    ->set('deploy_path', '/var/www/domain.com');

// Tasks

desc('Restart PHP-FPM service');
task('php-fpm:restart', function () {
    // The user must have rights for restart service
    // /etc/sudoers: username ALL=NOPASSWD:/bin/systemctl restart php-fpm.service
    run('sudo systemctl restart php-fpm.service');
});
after('deploy:symlink', 'php-fpm:restart');

desc('Deploy your project');
task('deploy', [
    'deploy:prepare',
    'deploy:lock',
    'deploy:release',
    'deploy:update_code',
    'deploy:shared',
    'deploy:writable',
    'deploy:vendors',
    'deploy:clear_paths',
    'deploy:symlink',
    'deploy:unlock',
    'cleanup',
    'success'
]);

// [Optional] if deploy fails automatically unlock.
after('deploy:failed', 'deploy:unlock');

我们应该将此文件作为一个模板来处理,该模板需要根据实际服务器进行调整。假设我们希望将我们的 MPHP7-CH16 应用程序部署到之前配置的45.76.88.214服务器上,我们可以通过如下调整deploy.php文件来实现:

<?php

namespace Deployer;
require 'recipe/common.php';

set('repository', 'https://github.com/ajzele/MPHP7-CH16.git');

server('production', '45.76.88.214')
    ->user('root')
    ->identityFile()
    ->set('deploy_path', '/var/www/MPHP7')
    ->set('branch', 'master')
    ->stage('production');

desc('Symlink html directory');
task('web:symlink', function () {
    run('ln -sf /var/www/MPHP7/current /var/www/html');
});

desc('Restart Apache service');
task('apache:restart', function () {
    run('service apache2 restart');
});

after('deploy:symlink', 'web:symlink');
after('web:symlink', 'apache:restart');

desc('Deploy your project');
task('deploy', [
    'deploy:prepare',
    'deploy:lock',
    'deploy:release',
    'deploy:update_code',
    'deploy:shared',
    'deploy:writable',
    //'deploy:vendors',
    'deploy:clear_paths',
    'deploy:symlink',
    'deploy:unlock',
    'cleanup',
    'success'
]);

after('deploy:failed', 'deploy:unlock');

我们使用set()函数来配置 git 存储库的位置。然后,server()函数定义了我们称之为production的单个服务器,位于 45.76.88.214 IP 之后。identityFile()只是告诉系统使用 SSH 密钥而不是 SSH 连接的密码。在服务器旁边,我们定义了两个自定义任务,web:symlinkapache:restart。这些确保了从部署者的/var/www/MPHP7/current/目录到我们的/var/www/html/目录的正确映射。after()函数调用只是定义了两个自定义作业应该执行的顺序,这是在部署人员的deploy:symlink事件之后。

要执行修改后的 deploy.php,我们使用以下控制台命令:

dep deploy production

这将为我们提供以下输出:

为了确认部署成功,打开http://45.76.88.214/应该给我们以下页面:

这个简单的部署器脚本为我们提供了一种强大的方法,可以自动将代码从存储库部署到服务器中。考虑到部署人员的server()功能,将其扩展到多个服务器是很容易的。

连续积分

持续集成背后的理念是以易于监督的方式将构建、测试和发布过程绑定在一起;这里我们不是在谈论编译代码。对于 PHP,我们倾向于将其与应用程序所需的各种配置联系起来。

尽管如此,持续整合的一些优点包括:

  • 通过静态代码分析实现自动代码覆盖率和质量检查
  • 通过在每次开发人员代码推送之后运行自动化
  • 通过单元和行为测试自动检测错误代码
  • 缩短应用程序发布周期
  • 提高整个项目的可视性

有十几种持续集成工具可供选择,其中一些工具包括:

如果说这些工具中有一个比其他工具更好,那是不公平的。尽管 Jenkins 在 PHP 方面似乎比其他工具稍微多一些

詹金斯

Jenkins 是一个开源、自包含、跨平台、随时可以运行基于 Java 的自动化服务器。Jenkins 定期发布两个版本:长期支持LTS)和每周发布。LTS 版本给人一种企业友好的感觉,其中包括:

在开箱即用的情况下,Jenkins 并没有专门为 PHP 代码做任何事情,而这正是插件混合的地方。

rich Jenkins 插件系统使我们能够轻松安装插件,以使用以下 PHP 工具:

这些工具的插件会影响 Jenkins 能够为我们持续运行的自动化测试。关于代码部署的部分通常与语言无关。深入了解插件安装的细节和 Jenkins 的总体使用,这是一本属于 Jenkins 的书的主题。这里的要点是了解在应用程序生命周期中持续集成的重要性和作用,以及提高对可用工具的认识。

See https://jenkins.io/doc/ and https://plugins.jenkins.io/ for more information.

总结

在本章中,我们讨论了应用程序周围的一些非编码要素。虽然开发人员倾向于避免大部分与系统操作相关的活动,但服务器及其设置的实际操作经验在部署和快速停机响应方面具有巨大优势。在我们的工作范围内划定一条不属于我的工作界线总是一个很危险的过程。与系统运营部门密切合作,为我们的应用程序增加了一层质量。最终用户可能会将其视为应用程序本身而不是其基础设施中的故障的层。托管、资源调配和部署已成为每个开发人员都需要熟悉的主题。围绕这些活动提供的工具在可用性和易用性方面似乎相当令人满意。

在整本书中,我们涵盖了广泛而看似独立的主题。这些告诉我们,构建应用程序几乎是一项简单而快速的任务。了解 PHP 语言本身的来龙去脉并不意味着有质量的软件。为我们的代码提供结构是模块化的最初标志之一,这反过来又减少了技术债务的影响。这就是标准和设计模式发挥重要作用的地方。毫无疑问,测试被证明是每个应用程序的重要组成部分。幸运的是,PHP 生态系统提供了丰富的测试框架,可以轻松涵盖 TDD 和 BDD 样式。随着 PHP7 中添加了大量新功能,编写高质量的 PHP 应用程序变得前所未有的容易。

希望到目前为止,我们对 PHP 及其生态系统以及构成高质量应用程序的各种其他基本部分了解得足够多,以便能够熟练地开发它们。