如何部署在Ubuntu 14.04使用Ansible高级PHP应用程序

在本教程中,我们将介绍如何设置SSH密钥,支持代码部署/发布工具,配置系统防火墙,配置和配置数据库(包括密码),并设置任务调度程序(crons)和队列守护进程。在本教程的最后目标是让你有与上述高级配置全面的PHP应用程序服务器。

介绍

本教程是关于在Ubuntu 14.04上使用Ansible部署PHP应用程序的系列的第二部分。 第一个教程涵盖了部署应用程序的基本步骤,是本教程中列出的步骤的起点。

在本教程中,我们将介绍设置SSH密钥以支持代码部署/发布工具,配置系统防火墙,配置和配置数据库(包括密码!),以及设置任务调度程序(cron)和队列守护程序。 本教程结束时的目标是让您拥有一个具有上述高级配置的完全正常的PHP应用程序服务器。

就像过去的教程中,我们将使用Laravel框架作为示例PHP应用程序。 但是,如果您已经拥有自己的框架和应用程序,则可以轻松修改这些指令以支持其他框架和应用程序。

先决条件

本教程直接从年底是继该系列中的第一个教程 ,所有的配置和该教程中生成的文件是必需的。 如果您还没有完成该教程,请先继续,然后再继续学习本教程。

第1步 - 切换应用程序存储库

在这一步中,我们将把Git仓库更新为一个稍微自定义的示例仓库。

因为默认的Laravel安装不需要我们将在本教程中设置的高级功能,我们将把现有的存储库从标准存储库切换到添加了一些调试代码的示例存储库,只是为了显示什么时候工作。 我们将使用该库位于https://github.com/do-community/do-ansible-adv-php

如果你还没有这样做的话,将目录切换到ansible-php从以前的教程。

cd ~/ansible-php/

打开我们现有的剧本进行编辑。

nano php.yml

找到并更新“克隆git存储库”任务,因此它看起来像这样。

更新了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

当它运行完毕后,访问你的服务器在Web浏览器(如http:// your_server_ip / )。 您应该看到一个消息,说“找不到驱动程序”。

这意味着我们已成功替换了示例存储库的默认存储库,但应用程序无法连接到数据库。 这是我们期望在这里看到的,我们将在教程中稍后安装和设置数据库。

第2步 - 设置部署的SSH密钥

在此步骤中,我们将设置可用于应用程序代码部署脚本的SSH密钥。

虽然Ansible是伟大的维护配置和设置服务器和应用程序,如工具特使火箭人常常被用来推动代码更改到你的服务器,并运行应用程序远程命令。 大多数这些工具需要一个SSH连接,可以直接访问应用程序安装。 在我们的例子中,这意味着我们需要配置SSH密钥为www-data的用户。

我们将需要您希望推送代码的用户的公钥文件。 此文件通常在找到~/.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 (或您的首选shell)。

新的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完成后,你应该能够使用SSH到www-data的用户。

ssh www-data@your_server_ip

如果你成功登录,它的工作! 现在,您可以通过输入登录背出logout或按CTRL + D。

对于本教程中的任何其他步骤,我们不需要使用该连接,但如果您正在设置其他工具(如上所述),或者根据需要进行常规调试和应用程序维护,这将非常有用。

第3步 - 配置防火墙

在此步骤中,我们将在服务器上配置防火墙,以仅允许HTTP和SSH的连接。

Ubuntu的14.04自带的默认安装UFW( 单纯性防火墙 ),并Ansible与支持它ufw模块。 它具有许多强大的功能,并被设计为尽可能简单。 它非常适合仅需要打开几个端口的自包含Web服务器。 在我们的例子中,我们想要端口80(HTTP)和端口22(SSH)打开。 您还可能需要端口443用于HTTPS。

ufw模块有许多其中执行不同的任务不同的选择。 我们需要执行的不同任务是:

  1. 默认情况下,启用UFW并拒绝所有传入流量。

  2. 打开SSH端口但速率限制它以防止暴力攻击。

  3. 打开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-servermysql-client ,和php5-mysql 我们还需要python-mysqldb这样Ansible可以与MySQL进行通信。

当我们添加软件包,我们需要重启nginxphp5-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_ -prefaced模块(如mysql_dbmysql_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命令的原始输出,这样的变量选项: dbpwd.stdout

所述mysql_user命令接受用户的名称和所需的特权。 在我们的例子中,我们要创建一个用户调用laravel ,让他们在完全权限laravel表。 我们还需要告诉只有当运行任务dbpwd变量发生变化 ,在运行密码生成任务时将只。

任务应如下所示:

新的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:

. . .

不要运行的剧本呢!你可能已经注意到,尽管我们已经创建了MySQL用户和数据库,我们没有做的任何密码。 我们将在下一步中介绍。 当使用shell内Ansible任务,它始终是重要要记住,完成与输出交易的整个工作流程/运行它,以避免手动登录并重置前的状态任务的结果。

第6步 - 为数据库配置PHP应用程序

在此步骤中,我们将保存MySQL数据库密码进入.env应用程序文件。

就像我们在上一个教程一样,我们将更新.env文件,包括我们新创建的数据库凭据。 默认情况下Laravel的.env文件包含这些行:

Laravel .env文件
DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

我们可以离开DB_HOST线原样,但会使用下面的任务,这非常类似于我们在前面的教程用于设置任务更新其他三个APP_ENVAPP_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之前,我们增加了我们的密码生成任务文件已经存在,我们需要将密码保存到另一个文件。 生成任务可以查找该文件的存在(我们已在任务中设置)。 我们也将使用sudosudo_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:

. . .

再次,不运行的剧本呢!我们有一个步骤完成之前,我们可以运行的剧本。

第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:

. . .

最后,我们可以保存和运行剧本。

ansible-playbook php.yml --ask-sudo-pass

完成执行后,在浏览器中刷新页面,您应该会看到一条消息:

your_server_ip /'> HTTP:// your_server_ip /
Queue: NO
Cron: NO

这意味着数据库设置正确并按预期工作,但我们尚未设置cron任务或队列守护程序。

第8步 - 配置cron任务

在此步骤中,我们将设置任何需要配置的cron任务。

Cron任务是按照设置的计划运行的命令,可用于为应用程序执行任意数量的任务,例如执行维护任务或发送电子邮件活动更新 - 基本上任何需要定期执行的操作,而无需手动用户干预。 Cron任务可以每分钟运行一次,或者根据需要频繁运行。

Laravel自带的工匠命令调用schedule:run在默认情况下,它被设计为每分钟运行,在应用程序中执行规定的计划任务。 这意味着如果我们的应用程序利用了这个特性,我们只需要添加一个cron任务。

Ansible具有cron与多个直接转换成可以通过cron配置的不同选择不同的选项模块:

  • job :要执行的命令。 如果state = present,则为必需。
  • minutehourdaymonth ,和weekday :分,小时,天,月,或当作业应当运行,分别是星期几。
  • special_timerebootyearlyannuallymonthlyweeklydailyhourly ):特殊时间规范昵称。

默认情况下,它将创建一个每分钟运行的任务,这是我们想要的。 这意味着我们想要的任务看起来像这样:

新的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

现在,在浏览器中刷新页面。 在一分钟内,它会更新为这样。

your_server_ip /'> HTTP:// your_server_ip /
Queue: NO
Cron: YES

这意味着cron正确地在后台工作。 作为示例应用程序的一部分,有一个cron作业,每分钟更新一个状态条目在数据库中,所以应用程序知道它正在运行。

第9步 - 配置队列守护程序

schedule:run第8步工匠命令,Laravel还带有可与启动队列工作queue:work --daemon工匠命令。 在这一步中,我们将为Laravel配置队列守护进程worker。

队列工作者与cron作业相似,因为它们在后台运行任务。 区别在于应用程序通过用户执行的操作或通过cron作业调度的任务将作业推送到队列中。 队列任务由工作者一次执行一个,并且当它们在队列中被找到时将被按需处理。 队列任务通常用于需要时间执行的工作,例如发送电子邮件或对外部服务进行API调用。

不同的是schedule:run命令,这不是一个需要每分钟运行一次的命令。 相反,它需要作为守护进程在后台不断运行。 要做到这一点的常用方法是使用第三方的包像supervisord,但是该方法需要了解如何配置和管理该系统。 还有一个更简单的使用cron和在实现它的方式run-one命令。

我们将创建一个cron项启动队列工作守护进程,并使用run-one来运行它。 这意味着它们的cron将启动过程中,它运行在第一时间,和任何后续的cron运行将由忽略run-one工人正在运行时。 只要工人停止, run-one将允许命令再次运行,并且队列工人将再次启动。 它是一个令人难以置信的简单和易于使用的方法,可以帮助您不需要学习如何配置和使用其他工具。

考虑到所有这些,我们将创建另一个cron任务来运行队列worker。

新的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

与之前一样,在浏览器中刷新页面。 一分钟后,它会更新为如下所示:

your_server_ip /'> HTTP:// your_server_ip /
Queue: YES
Cron: YES

这意味着队列worker正确地在后台工作。 我们在上一步中启动的cron作业将作业推送到队列中。 此作业在数据库运行时更新数据库,以显示其正在工作。

我们现在有一个工作示例Laravel应用程序,其中包括功能cron作业和队列工人。

结论

本教程涵盖了使用Ansible部署PHP应用程序时的一些更高级的主题。 所有使用的任务可以很容易地修改,以适应大多数PHP应用程序(取决于他们的具体要求),它应该给你一个良好的起点,为您的应用程序设置自己的剧本。

我们没有使用一个单一的SSH命令,本教程中(除了检查的一部分www-data的用户登录),和一切-包括MySQL用户密码-已自动成立。 遵循本教程后,您的应用程序已准备就绪,并支持推送代码更新的工具。