如何在Ubuntu14.04上使用Ansible部署高级PHP应用程序
介绍
本教程是关于在 Ubuntu 14.04 上使用 Ansible 部署 PHP 应用程序的系列教程的第二篇。 第一篇教程 涵盖了部署应用程序的基本步骤,是本教程中概述的步骤的起点。
在本教程中,我们将介绍设置 SSH 密钥以支持代码部署/发布工具、配置系统防火墙、配置和配置数据库(包括密码!),以及设置任务调度程序 (crons) 和队列守护程序。 本教程结束时的目标是让您拥有一个具有上述高级配置的功能齐全的 PHP 应用程序服务器。
像上一个教程一样,我们将使用 Laravel 框架 作为我们的示例 PHP 应用程序。 但是,如果您已经拥有自己的框架和应用程序,可以轻松修改这些说明以支持其他框架和应用程序。
先决条件
本教程是从系列第一个教程的结尾直接开始的,并且需要为该教程生成的所有配置和文件。 如果您尚未完成该教程,请先完成,然后再继续本教程。
第 1 步 — 切换应用程序存储库
在这一步中,我们会将 Git 存储库更新为稍微定制的示例存储库。
因为默认的 Laravel 安装不需要我们将在本教程中设置的高级功能,所以我们将把现有的存储库从标准存储库切换到添加了一些调试代码的示例存储库,只是为了显示什么时候工作正常. 我们将使用的存储库位于 https://github.com/do-community/do-ansible-adv-php
。
如果您还没有这样做,请将目录更改为上一教程中的 ansible-php
。
cd ~/ansible-php/
打开我们现有的剧本进行编辑。
nano php.yml
找到并更新“Clone git repository”任务,看起来像这样。
更新了 Ansible 任务
- name: Clone git repository git: > dest=/var/www/laravel repo=https://github.com/do-community/do-ansible-adv-php update=yes version=example sudo: yes sudo_user: www-data register: cloned
保存并运行剧本。
ansible-playbook php.yml --ask-sudo-pass
完成运行后,在您的网络浏览器中访问您的服务器(即 http://your_server_ip/
)。 您应该会看到一条消息,显示 “找不到驱动程序”。
这意味着我们已经成功地将默认存储库换成了示例存储库,但应用程序无法连接到数据库。 这是我们希望在这里看到的,我们将在本教程后面安装和设置数据库。
第 2 步 — 为部署设置 SSH 密钥
在这一步中,我们将设置可用于应用程序代码部署脚本的 SSH 密钥。
虽然 Ansible 非常适合维护配置和设置服务器和应用程序,但 Envoy 和 Rocketeer 等工具通常用于将代码更改推送到您的服务器并远程运行应用程序命令。 这些工具中的大多数都需要可以直接访问应用程序安装的 SSH 连接。 在我们的例子中,这意味着我们需要为 www-data
用户配置 SSH 密钥。
我们将需要您希望从中推送代码的用户的公钥文件。 该文件通常位于 ~/.ssh/id_rsa.pub
。 将该文件复制到 ansible-php
目录中。
cp ~/.ssh/id_rsa.pub ~/ansible-php/deploykey.pub
我们可以使用 Ansible authorized_key
模块在 /var/www/.ssh/authorized_keys
中安装我们的公钥,这将允许部署工具连接和访问我们的应用程序。 配置只需要知道密钥在哪里,使用查找,以及需要为用户安装密钥(在我们的例子中是 www-data
)。
新的 Ansible 任务
- name: Copy public key into /var/www authorized_key: user=www-data key="{{ lookup('file', 'deploykey.pub') }}"
我们还需要设置www-data
用户的shell,这样我们才能真正登录。 否则,SSH 将允许连接,但不会向用户提供 shell。 这可以使用 user
模块来完成,并将外壳设置为 /bin/bash
(或您喜欢的外壳)。
新的 Ansible 任务
- name: Set www-data user shell user: name=www-data shell=/bin/bash
现在,打开剧本进行编辑以添加新任务。
nano php.yml
将上述任务添加到您的 php.yml
剧本中; 文件的结尾应与以下内容匹配。 添加的内容以红色突出显示。
更新了 php.yml
. . . - name: Configure nginx template: src=nginx.conf dest=/etc/nginx/sites-available/default notify: - restart php5-fpm - restart nginx - name: Copy public key into /var/www authorized_key: user=www-data key="{{ lookup('file', 'deploykey.pub') }}" - name: Set www-data user shell user: name=www-data shell=/bin/bash handlers: . . .
保存并运行剧本。
ansible-playbook php.yml --ask-sudo-pass
Ansible 完成后,您应该能够使用 www-data
用户进行 SSH 连接。
ssh www-data@your_server_ip
如果您成功登录,它正在工作! 您现在可以通过输入 logout
或按 CTRL+D 来注销。
我们不需要在本教程中的任何其他步骤中使用该连接,但如果您要设置其他工具(如上所述),或者根据需要进行常规调试和应用程序维护,它将很有用。
第 3 步 — 配置防火墙
在这一步中,我们将在服务器上配置防火墙以仅允许 HTTP 和 SSH 连接。
Ubuntu 14.04 默认安装了 UFW (Uncomplicated Firewall),Ansible 通过 ufw
模块支持它。 它具有许多强大的功能,并且设计得尽可能简单。 它非常适合只需要打开几个端口的独立 Web 服务器。 在我们的例子中,我们希望端口 80 (HTTP) 和端口 22 (SSH) 开放。 您可能还需要 443 端口用于 HTTPS。
ufw
模块有许多不同的选项来执行不同的任务。 我们需要执行的不同任务是:
- 默认情况下启用 UFW 并拒绝所有传入流量。
- 打开 SSH 端口但对其进行速率限制以防止暴力攻击。
- 打开 HTTP 端口。
这可以分别通过以下任务来完成。
新的 Ansible 任务
- name: Enable UFW ufw: direction=incoming policy=deny state=enabled - name: UFW limit SSH ufw: rule=limit port=ssh - name: UFW open HTTP ufw: rule=allow port=http
和之前一样,打开php.yml
文件进行编辑。
nano php.yml
将上述任务添加到剧本中; 文件的结尾应与以下内容匹配。
更新了 php.yml
. . . - name: Copy public key into /var/www authorized_key: user=www-data key="{{ lookup('file', 'deploykey.pub') }}" - name: Set www-data user shell user: name=www-data shell=/bin/bash - name: Enable UFW ufw: direction=incoming policy=deny state=enabled - name: UFW limit SSH ufw: rule=limit port=ssh - name: UFW open HTTP ufw: rule=allow port=http handlers: . . .
保存并运行剧本。
ansible-playbook php.yml --ask-sudo-pass
成功完成后,您应该仍然能够通过 SSH(使用 Ansible)或 HTTP 连接到您的服务器; 其他端口现在将被阻止。
您可以通过运行以下命令随时验证 UFW 的状态:
ansible php --sudo --ask-sudo-pass -m shell -a "ufw status verbose"
分解上面的 Ansible 命令:
ansible
:运行原始 Ansible 任务,无需剧本。php
:针对该组中的主机运行任务。--sudo
:运行命令为sudo
。--ask-sudo-pass
:提示输入sudo
密码。-m shell
:运行shell
模块。-a "ufw status verbose"
:要传入模块的选项。 因为是shell
命令,我们传递原始命令(即ufw status verbose
) 直接输入,没有任何key=value
选项。
它应该返回这样的东西。
UFW状态输出
your_server_ip | success | rc=0 >> Status: active Logging: on (low) Default: deny (incoming), allow (outgoing), disabled (routed) New profiles: skip To Action From -- ------ ---- 22 LIMIT IN Anywhere 80 ALLOW IN Anywhere 22 (v6) LIMIT IN Anywhere (v6) 80 (v6) ALLOW IN Anywhere (v6)
第 4 步 — 安装 MySQL 包
在这一步中,我们将设置一个 MySQL 数据库供我们的应用程序使用。
第一步是确保 MySQL 安装在我们的服务器上,只需将所需的包添加到我们剧本顶部的安装包任务中。 我们需要的包是mysql-server
、mysql-client
和php5-mysql
。 我们还需要 python-mysqldb
以便 Ansible 可以与 MySQL 通信。
在添加包时,我们需要重新启动 nginx
和 php5-fpm
以确保应用程序可以使用新包。 在这种情况下,我们需要 MySQL 对 PHP 可用,以便它可以连接到数据库。
Ansible 的一项奇妙之处在于,您可以修改任何任务并重新运行您的剧本,并且将应用更改。 这包括选项列表,就像我们在 apt
任务中所做的那样。
和之前一样,打开php.yml
文件进行编辑。
nano php.yml
找到 install packages
任务,并更新它以包含上述包:
更新了 php.yml
. . . - name: install packages apt: name={{ item }} update_cache=yes state=latest with_items: - git - mcrypt - nginx - php5-cli - php5-curl - php5-fpm - php5-intl - php5-json - php5-mcrypt - php5-sqlite - sqlite3 - mysql-server - mysql-client - php5-mysql - python-mysqldb notify: - restart php5-fpm - restart nginx . . .
保存并运行剧本:
ansible-playbook php.yml --ask-sudo-pass
第 5 步 — 设置 MySQL 数据库
在这一步中,我们将为我们的应用程序创建一个 MySQL 数据库。
Ansible 可以使用 mysql_
开头的模块直接与 MySQL 对话(例如 mysql_db
、mysql_user
)。 mysql_db
模块提供了一种方法来确保具有特定名称的数据库存在,因此我们可以使用这样的任务来创建数据库。
新的 Ansible 任务
- name: Create MySQL DB mysql_db: name=laravel state=present
我们还需要一个具有已知密码的有效用户帐户,以允许我们的应用程序连接到数据库。 一种方法是在本地生成密码并将其保存在我们的 Ansible playbook 中,但这是不安全的,还有更好的方法。
我们将在服务器本身上使用 Ansible 生成密码,并在需要的地方直接使用它。 要生成密码,我们将使用 makepasswd
命令行工具,并要求输入 32 个字符的密码。 因为 makepasswd
在 Ubuntu 上不是默认设置,所以我们也需要将其添加到包列表中。
我们还将告诉 Ansible 记住命令的输出(即 密码),所以我们可以稍后在我们的剧本中使用它。 但是,因为 Ansible 不知道它是否已经运行了 shell
命令,所以我们在运行该命令时也会创建一个文件。 Ansible 将检查文件是否存在,如果存在,它将假定该命令已经运行并且不会再次运行它。
任务如下所示:
新的 Ansible 任务
- name: Generate DB password shell: makepasswd --chars=32 args: creates: /var/www/laravel/.dbpw register: dbpwd
接下来,我们需要使用我们指定的密码创建实际的 MySQL 数据库用户。 这是使用 mysql_user
模块完成的,我们可以在密码生成任务期间定义的变量上使用 stdout
选项来获取 shell 命令的原始输出,如下所示: [X195X ]。
mysql_user
命令接受用户名和所需权限。 在我们的例子中,我们要创建一个名为 laravel
的用户,并赋予他们对 laravel
表的完全权限。 我们还需要告诉任务仅在 dbpwd
变量具有 changed 时运行,这将仅在运行密码生成任务时运行。
任务应如下所示:
新的 Ansible 任务
- name: Create MySQL User mysql_user: name=laravel password={{ dbpwd.stdout }} priv=laravel.*:ALL state=present when: dbpwd.changed
把这些放在一起,打开php.yml
文件进行编辑,这样我们就可以添加上面的任务了。
nano php.yml
首先,找到 install packages
任务,并更新它以包含 makepasswd
包。
更新了 php.yml
. . . - name: install packages apt: name={{ item }} update_cache=yes state=latest with_items: - git - mcrypt - nginx - php5-cli - php5-curl - php5-fpm - php5-intl - php5-json - php5-mcrypt - php5-sqlite - sqlite3 - mysql-server - mysql-client - php5-mysql - python-mysqldb - makepasswd notify: - restart php5-fpm - restart nginx . . .
然后,在底部添加密码生成、MySQL数据库创建和用户创建任务。
更新了 php.yml
. . . - name: UFW limit SSH ufw: rule=limit port=ssh - name: UFW open HTTP ufw: rule=allow port=http - name: Create MySQL DB mysql_db: name=laravel state=present - name: Generate DB password shell: makepasswd --chars=32 args: creates: /var/www/laravel/.dbpw register: dbpwd - name: Create MySQL User mysql_user: name=laravel password={{ dbpwd.stdout }} priv=laravel.*:ALL state=present when: dbpwd.changed handlers: . . .
暂时不要运行 playbook! 你可能已经注意到,虽然我们已经创建了 MySQL 用户和数据库,但我们并没有对密码做任何事情。 我们将在下一步中介绍。 在 Ansible 中使用 shell
任务时,务必记住在运行任务之前完成处理任务输出/结果的整个工作流程,以避免手动登录和重置状态。
第 6 步 — 为数据库配置 PHP 应用程序
在这一步中,我们将 MySQL 数据库密码保存到应用程序的 .env
文件中。
就像我们在上一个教程中所做的那样,我们将更新 .env
文件以包含我们新创建的数据库凭据。 默认情况下,Laravel 的 .env
文件包含以下几行:
Laravel .env 文件
DB_HOST=localhost DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret
我们可以保留 DB_HOST
行,但将使用以下任务更新其他三个,这与我们在上一个教程中设置 APP_ENV
和 APP_DEBUG
。
新的 Ansible 任务
- name: set DB_DATABASE lineinfile: dest=/var/www/laravel/.env regexp='^DB_DATABASE=' line=DB_DATABASE=laravel - name: set DB_USERNAME lineinfile: dest=/var/www/laravel/.env regexp='^DB_USERNAME=' line=DB_USERNAME=laravel - name: set DB_PASSWORD lineinfile: dest=/var/www/laravel/.env regexp='^DB_PASSWORD=' line=DB_PASSWORD={{ dbpwd.stdout }} when: dbpwd.changed
正如我们对 MySQL 用户创建任务所做的那样,我们使用生成的密码变量 (dbpwd.stdout
) 来使用密码填充文件,并添加了 when
选项以确保它只运行当 dbpwd
发生变化时。
现在,因为 .env
文件在我们添加密码生成任务之前已经存在,我们需要将密码保存到另一个文件中。 生成任务可以查找该文件的存在(我们已经在任务中设置)。 我们还将使用 sudo
和 sudo_user
选项告诉 Ansible 以 www-data
用户身份创建文件。
新的 Ansible 任务
- name: Save dbpw file lineinfile: dest=/var/www/laravel/.dbpw line="{{ dbpwd.stdout }}" create=yes state=present sudo: yes sudo_user: www-data when: dbpwd.changed
打开 php.yml
文件进行编辑。
nano php.yml
将上述任务添加到剧本中; 文件的结尾应与以下内容匹配。
更新了 php.yml
. . . - name: Create MySQL User mysql_user: name=laravel password={{ dbpwd.stdout }} priv=laravel.*:ALL state=present when: dbpwd.changed - name: set DB_DATABASE lineinfile: dest=/var/www/laravel/.env regexp='^DB_DATABASE=' line=DB_DATABASE=laravel - name: set DB_USERNAME lineinfile: dest=/var/www/laravel/.env regexp='^DB_USERNAME=' line=DB_USERNAME=laravel - name: set DB_PASSWORD lineinfile: dest=/var/www/laravel/.env regexp='^DB_PASSWORD=' line=DB_PASSWORD={{ dbpwd.stdout }} when: dbpwd.changed - name: Save dbpw file lineinfile: dest=/var/www/laravel/.dbpw line="{{ dbpwd.stdout }}" create=yes state=present sudo: yes sudo_user: www-data when: dbpwd.changed handlers: . . .
再一次,暂时不要运行 playbook! 在运行 playbook 之前,我们还需要完成一个步骤。
第 7 步 — 迁移数据库
在这一步中,我们将运行数据库迁移来设置数据库表。
在 Laravel 中,这是通过运行 migrate
命令来完成的(即 php artisan migrate --force
) 在 Laravel 目录中。 请注意,我们添加了 --force
标志,因为 production
环境需要它。
执行此操作的 Ansible 任务如下所示。
新的 Ansible 任务
- name: Run artisan migrate shell: php /var/www/laravel/artisan migrate --force sudo: yes sudo_user: www-data when: dbpwd.changed
现在是时候更新我们的剧本了。 打开 php.yml
文件进行编辑。
nano php.yml
将上述任务添加到剧本中; 文件的结尾应与以下内容匹配。
更新了 php.yml
. . . - name: Save dbpw file lineinfile: dest=/var/www/laravel/.dbpw line="{{ dbpwd.stdout }}" create=yes state=present sudo: yes sudo_user: www-data when: dbpwd.changed - name: Run artisan migrate shell: php /var/www/laravel/artisan migrate --force sudo: yes sudo_user: www-data when: dbpwd.changed handlers: . . .
最后,我们可以保存并运行 playbook。
ansible-playbook php.yml --ask-sudo-pass
完成执行后,刷新浏览器中的页面,您应该会看到一条消息:
http://<^>your_server_ip<^>/
Queue: NO Cron: NO
这意味着数据库已正确设置并按预期工作,但我们尚未设置 cron 任务或队列守护程序。
第 8 步 — 配置 cron 任务
在这一步中,我们将设置任何需要配置的 cron 任务。
Cron 任务是按设定的时间表运行的命令,可用于为您的应用程序执行任意数量的任务,例如执行维护任务或发送电子邮件活动更新——本质上是任何需要定期完成而无需用户手动干预的任务。 Cron 任务可以每分钟运行一次,也可以根据需要不频繁运行。
Laravel 默认带有一个名为 schedule:run
的 Artisan 命令,该命令旨在每分钟运行一次,并在应用程序中执行定义的计划任务。 这意味着如果我们的应用程序利用了这个特性,我们只需要添加一个 cron 任务。
Ansible 有一个 cron
模块,其中包含许多不同的选项,这些选项直接转换为您可以通过 cron 配置的不同选项:
job
:要执行的命令。 如果 state=present 是必需的。minute
、hour
、day
、month
和weekday
:分、时、日、月或星期几作业应该分别运行的时间。special_time
(reboot
,yearly
,annually
,monthly
,weekly
,daily
, [ X77X]):特殊时间规范昵称。
默认情况下,它将创建一个每分钟运行的任务,这正是我们想要的。 这意味着我们想要的任务如下所示:
新的 Ansible 任务
- name: Laravel Scheduler cron: > job="run-one php /var/www/laravel/artisan schedule:run 1>> /dev/null 2>&1" state=present user=www-data name="php artisan schedule:run"
run-one
命令是 Ubuntu 中的一个小助手,可确保该命令只运行一次。 这意味着如果之前的 schedule:run
命令仍在运行,它将不会再次运行。 这有助于避免 cron 任务被锁定在循环中的情况,并且随着时间的推移,相同任务的越来越多的实例被启动,直到服务器耗尽资源。
和之前一样,打开php.yml
文件进行编辑。
nano php.yml
将上述任务添加到剧本中; 文件的结尾应与以下内容匹配。
更新了 php.yml
. . . - name: Run artisan migrate shell: php /var/www/laravel/artisan migrate --force sudo: yes sudo_user: www-data when: dbpwd.changed - name: Laravel Scheduler cron: > job="run-one php /var/www/laravel/artisan schedule:run 1>> /dev/null 2>&1" state=present user=www-data name="php artisan schedule:run" handlers: . . .
保存并运行剧本:
ansible-playbook php.yml --ask-sudo-pass
现在,在浏览器中刷新页面。 一分钟后,它将更新为如下所示。
http://<^>your_server_ip<^>/
Queue: NO Cron: YES
这意味着 cron 在后台正常工作。 作为示例应用程序的一部分,有一个 cron 作业每分钟运行一次,更新数据库中的状态条目,以便应用程序知道它正在运行。
第 9 步 — 配置队列守护程序
与第 8 步中的 schedule:run
Artisan 命令一样,Laravel 还附带了一个队列工作器,可以使用 queue:work --daemon
Artisan 命令启动。 在这一步中,我们将为 Laravel 配置队列守护进程。
队列工作者类似于 cron 作业,因为它们在后台运行任务。 不同之处在于应用程序通过用户执行的操作或通过 cron 作业安排的任务将作业推送到队列中。 队列任务由worker一次执行一个,当在队列中找到时会按需处理。 队列任务通常用于需要时间执行的工作,例如发送电子邮件或对外部服务进行 API 调用。
与 schedule:run
命令不同,这不是每分钟都需要运行的命令。 相反,它需要不断地在后台作为守护进程运行。 一种常见的方法是使用第三方包,如 supervisord,但该方法需要了解如何配置和管理所述系统。 使用 cron 和 run-one
命令有一种更简单的方法来实现它。
我们将创建一个 cron 条目来启动队列工作守护程序,并使用 run-one
来运行它。 这意味着 cron 将在它第一次运行时启动进程,并且任何后续的 cron 运行都将在 worker 运行时被 run-one
忽略。 一旦 worker 停止,run-one
将允许命令再次运行,队列 worker 将再次启动。 这是一种非常简单易用的方法,使您无需学习如何配置和使用其他工具。
考虑到所有这些,我们将创建另一个 cron 任务来运行我们的队列工作者。
新的 Ansible 任务
- name: Laravel Queue Worker cron: > job="run-one php /var/www/laravel/artisan queue:work --daemon --sleep=30 --delay=60 --tries=3 1>> /dev/null 2>&1" state=present user=www-data name="Laravel Queue Worker"
和之前一样,打开php.yml
文件进行编辑。
nano php.yml
将上述任务添加到剧本中; 文件的结尾应与以下内容匹配:
更新了 php.yml
. . . - name: Laravel Scheduler cron: > job="run-one php /var/www/laravel/artisan schedule:run 1>> /dev/null 2>&1" state=present user=www-data name="php artisan schedule:run" - name: Laravel Queue Worker cron: > job="run-one php /var/www/laravel/artisan queue:work --daemon --sleep=30 --delay=60 --tries=3 1>> /dev/null 2>&1" state=present user=www-data name="Laravel Queue Worker" handlers: . . .
保存并运行剧本:
ansible-playbook php.yml --ask-sudo-pass
像以前一样,在浏览器中刷新页面。 一分钟后,它将更新为如下所示:
http://<^>your_server_ip<^>/
Queue: YES Cron: YES
这意味着队列工作者正在后台正确工作。 我们在最后一步启动的 cron 作业将作业推送到队列中。 此作业在运行时更新数据库以显示它正在工作。
我们现在有一个工作示例 Laravel 应用程序,其中包括正常运行的 cron 作业和队列工作者。
结论
本教程涵盖了使用 Ansible 部署 PHP 应用程序时的一些更高级的主题。 使用的所有任务都可以轻松修改以适应大多数 PHP 应用程序(取决于它们的特定要求),它应该为您提供一个很好的起点,为您的应用程序设置自己的剧本。
作为本教程的一部分,我们没有使用单个 SSH 命令(除了检查 www-data
用户登录),并且所有内容(包括 MySQL 用户密码)都已自动设置。 完成本教程后,您的应用程序已准备就绪并支持推送代码更新的工具。