前言
本教程介绍如何使用Ansible部署一个简单的PHP应用。到本文结束时,你将有一台搭建在VPS(云主机)上的Web服务器,上面运行着一个简单的PHP,而整个过程完全不需要你SSH到Web服务器所在的VPS敲命令。
我们的PHP应用使用的是Laravel框架。如果你用的是其他框架,整个操作过程其实也差不多。
准备工作
我们将使用Ansible在一个Ubuntu 14.04云主机上安装配置Nginx、PHP以及其他服务。你需要对Ansible有一定的了解。如果你对Ansible不太熟,可以先参阅这篇Ansible基础教程(https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-ansible-on-an-ubuntu-12-04-vps)。
在开始操作之前,你需要满足如下条件:
一台安装了Ubuntu 14.04的云主机,硬盘大小随意,需要有IP地址。本教程将使用your_server_ip指代这个IP地址。
另一台安装了Ubuntu 14.04的云主机,将用于安装Ansible。整个登陆操作都将在这一台云主机上进行。
两台云主机都设置了可以sudo的非root用户。
Ansible云主机上配置好SSH密钥。
步骤1:安装Ansible
Ansible的安装可以很简单的通过PPA来做。安装了相应的PPA后直接使用apt安装Ansible。
首先,用apt-add-repository命令添加ppa:
sudo apt-add-repository ppa:ansible/ansible
然后更新一下apt缓存:
sudo apt-get update
然后安装Ansible:
sudo apt-get install ansible
Ansible装好之后,创建一个新的目录,在里面做一些基本设置。Ansible默认使用的hosts文件位置在/etc/ansible/hosts,这里面列出了其管理的所有服务器。这个文件是全局性的,不是我们想要的;我们想要一个本地的,就需要自己创建。
我们直接在自己的工作目录下创建一个新的Ansible配置文件,这个文件会告诉Ansible在同一目录下查找hosts文件。
首先,创建一个新目录:
mkdir ~/ansible-php
进去:
cd ~/ansible-php/
创建一个ansible.cfg文件,用nano或者其他编辑器打开它:
nano ansible.cfg
在[defaults]组下添加一个hostfile配置选项,将该项赋值为hosts。简单来说就是把下面的内容复制到你的文件里:
ansible.cfg
[defaults]
hostfile = hosts
保存退出。然后,创建hosts文件。这里面将列出你要控制管理的PHP云主机的IP。
nano hosts
把下面的内容复制粘贴到hosts文件里。里面有一个php组,下面设置了你的服务器ipyour_server_ip以及你在这台PHP服务器上的非root用户名sammy(别忘了替换成你自己的IP和用户名):
hosts
[php]
your_server_ip ansible_ssh_user=sammy
保存退出。现在来做个简单的检查,看看Ansible是否能够连接到上面这个服务器。Ping一下php看看:
ansible php -m ping
如果你是第一次从本机登陆,可能会返回SSH认证的提示。SSH认证完成后,这次ping应该返回一个成功的结果,看起来差不多这样:
111.111.111.111 | success >> {
"changed": false,
"ping": "pong"
}
Ansible的安装配置这样就可以了。现在去设置我们的Web服务器。
步骤2:安装所需的软件包
我们将使用Ansible和apt来在Web服务器上安装一些系统软件包,包括Git、nginx、sqlite3、mcrypt、以及一些php5-*的包。
在安装上述软件包之前,我们需要先创建一个简单的playbook。创建一个php.yml作为我们的playbook:
nano php.yml
把下面的配置内容复制粘贴进去。前两行设定了hosts组(php)以及运行该hosts组的默认权限sudo。下面的内容添加了我们所需要的软件包。如果你想要直接安装自己的应用,则可以修改这部分内容;如果你想先跟着我们安装我们的Laravel应用,则可以照单全收:
---
- hosts: php
sudo: yes
tasks:
- 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
保存退出。最后,运行ansible-playbook命令,该命令会在目标云主机上安装所有的软件包。如果你的目标云主机是通过密码登陆的,则需要在命令里包含--ask-sudo-pass选项。
ansible-playbook php.yml --ask-sudo-pass
步骤3:修改系统配置文件
本章将修改PHP云主机上的一些系统配置文件。最重要的一个配置是php5-fpm的cgi.fix_pathinfo选项,因为这个选项的默认值会造成安全隐患。
我先简单介绍一下整个步骤,然后贴出整个php.yml以方便复制粘贴。
Lineinfile模块可用于确保配置的值完全符合我们的期待,这需要用到正则表达式让Ansible定位到我们想要修改的参数。修改之后,我们需要重启php5-fpm和nginx让配置生效,这需要用两个handler来实现。Handler很适合做此类任务,不仅因为它们精确的触发时机(任务变更时),而且因为它们在playbook的末尾执行,所以多个任务可以调用同一个handler,每一个handler仅会执行一次。
这部分内容看起来是这样的:
- name: ensure php5-fpm cgi.fix_pathinfo=0
lineinfile: dest=/etc/php5/fpm/php.ini regexp='^(.*)cgi.fix_pathinfo=' line=cgi.fix_pathinfo=0
notify:
- restart php5-fpm
- restart nginx
handlers:
- name: restart php5-fpm
service: name=php5-fpm state=restarted
- name: restart nginx
service: name=nginx state=restarted
注:Ansible 1.9.1版有一个bug,导致php5-fpm无法通过上面的service模块进行重启。临时的解决办法是改用shell命令,像这样:
- name: restart php5-fpm
shell: service php5-fpm restart
然后,我们需要确保php5-mcrypt模块处于开启状态(enabled)。这需要运行php5enmod脚本,检查20-mcrypt.ini文件是否在正确的位置。这里,我们的逻辑是让Ansible建立的任务尝试创建一个指定的文件,若文件已存在则不执行任务。
- name: enable php5 mcrypt module
shell: php5enmod mcrypt
args:
creates: /etc/php5/cli/conf.d/20-mcrypt.ini
就是这样。现在可以打开php.yml把上面的内容都粘贴进去了:
nano php.yml
整个文件现在看起来应该是这样的:
---
- hosts: php
sudo: yes
tasks:
- 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
- name: ensure php5-fpm cgi.fix_pathinfo=0
lineinfile: dest=/etc/php5/fpm/php.ini regexp='^(.*)cgi.fix_pathinfo=' line=cgi.fix_pathinfo=0
notify:
- restart php5-fpm
- restart nginx
- name: enable php5 mcrypt module
shell: php5enmod mcrypt
args:
creates: /etc/php5/cli/conf.d/20-mcrypt.ini
handlers:
- name: restart php5-fpm
service: name=php5-fpm state=restarted
- name: restart nginx
service: name=nginx state=restarted
最后,运行playbook:
ansible-playbook php.yml --ask-sudo-pass
我们的Web服务器主机上现在已经安装了所有需要的软件包,并完成了基本的系统配置。
步骤4:获取Git Repo
本章将把Laravel框架的repo给clone到Web服务器主机上。跟上一章一样,我先一步一步的说明,最后再给出完整的php.yml供复制粘贴。
在进行git repo的clone之前,先确认一下/var/www是否存在。这可以用file模块创建的任务来实现:
- name: create /var/www/ directory
file: dest=/var/www/ state=directory owner=www-data group=www-data mode=0700
如上所述,我们需要用git模块来进行repo的拉取,整个进程只需要一个git clone命令所需的repo源地址。我们还需要定义clone的目标目录,并在目标目录已经存在该repo时让Ansible取消拉取(设置update=no)。Laravel的Git repo url是:https://github.com/laravel/laravel.git。
另外,我们需要以www-data用户来执行这个任务以确保权限的正确性,这就需要Ansible以sudo命令切换到另一个用户下执行此命令。这部分内容看起来是这样的:
- name: Clone git repository
git: >
dest=/var/www/laravel
repo=https://github.com/laravel/laravel.git
update=no
sudo: yes
sudo_user: www-data
注:如果是基于SSH的repo,SSH host的验证过程可能会干扰任务的执行。可以添加一个accept_hostkey=yes配置项来避免这一情况。
就是这样。现在打开php.yml开始复制粘贴:
nano php.yml
前面的我就不贴了,只把本章内容放进来,如下(记得把handler留在文件末尾):
...
- name: enable php5 mcrypt module
shell: php5enmod mcrypt
args:
creates: /etc/php5/cli/conf.d/20-mcrypt.ini
- name: create /var/www/ directory
file: dest=/var/www/ state=directory owner=www-data group=www-data mode=0700
- name: Clone git repository
git: >
dest=/var/www/laravel
repo=https://github.com/laravel/laravel.git
update=no
sudo: yes
sudo_user: www-data
handlers:
- name: restart php5-fpm
service: name=php5-fpm state=restarted
- name: restart nginx
service: name=nginx state=restarted
保存退出,运行之:
ansible-playbook php.yml --ask-sudo-pass
步骤5:用Composer创建应用
本章将使用Composer安装PHP应用以及其依赖项。
Composer有一个create-project命令,它可以安装所有需要的依赖项,并运行composer.json文件下post-create-project-cmd部分定义的项目创建动作。这可以确保应用安装的正确性。
我们可以用下面的这个Ansible任务来下载安装Composer到全局目录/usr/local/bin/composer。这样一来,访问这台云主机的其他人和应用也都可以使用Composer。
- name: install composer
shell: curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
args:
creates: /usr/local/bin/composer
安装Composer之后,我们就可以使用Composer模块了。我们将告诉Composer我们的项目位置(working_dir参数),然后运行create-project命令。我们还需要添加optimize_autoloader=no参数,因为create-project命令不支持这个标记。跟git命令一样,我们需要用www-data用户来执行这个任务以确保权限正确。这部分内容看起来是这样的:
- name: composer create-project
composer: command=create-project working_dir=/var/www/laravel optimize_autoloader=no
sudo: yes
sudo_user: www-data
注:create-project在一台刚建立的新主机上可能需要运行很久的时间,因为Composer缓存尚未建立,需要从头下载所有的东西。
就这样。现在打开php.yml文件开始复制粘贴……
nano php.yml
跟上一章一样,别忘了把handlers留在最后:
...
- name: Clone git repository
git: >
dest=/var/www/laravel
repo=https://github.com/laravel/laravel.git
update=no
sudo: yes
sudo_user: www-data
- name: install composer
shell: curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
args:
creates: /usr/local/bin/composer
- name: composer create-project
composer: command=create-project working_dir=/var/www/laravel optimize_autoloader=no
sudo: yes
sudo_user: www-data
handlers:
- name: restart php5-fpm
service: name=php5-fpm state=restarted
- name: restart nginx
service: name=nginx state=restarted
保存退出,运行playbook:
ansible-playbook php.yml --ask-sudo-pass
这儿有个问题:如果我们现在再运行一次Ansible,会发生什么事呢?
composer create-project会再运行一次。就Laravel而言,这会生成一个新的APP_KEY。所以,我们需要该任务仅在新clone之后运行一次。要实现这个效果,可以在git clone任务的运行结果上注册一个变量,然后在composer create-project任务里检查这个变量。如果git clone那儿变了,则运行composer create-project;否则,则跳过。
打开php.yml进行编辑:
nano php.yml
找到git clone的任务,在下面添加一个register选项,将任务执行的结果保存为cloned变量:
- name: Clone git repository
git: >
dest=/var/www/laravel
repo=https://github.com/laravel/laravel.git
update=no
sudo: yes
sudo_user: www-data
register: cloned
再找到composer create-project任务,在下面添加一个when选项,检查cloned变量是否为“changed”:
- name: composer create-project
composer: command=create-project working_dir=/var/www/laravel optimize_autoloader=no
sudo: yes
sudo_user: www-data
when: cloned|changed
保存退出,运行playbook:
ansible-playbook php.yml --ask-sudo-pass
现在运行的Composer就不会改变APP_KEY了。
注:有些版本的Ansible的composer似乎有bug,会忽略脚本的执行,其输出是“OK”而不是“Changed”,但却不会安装依赖项。
步骤6:更新环境变量
本章将为应用更新一些环境变量。
Laravel有一个默认的.env文件,其中将APP_ENV设为local,APP_DEBUG设为true。我们需要把local改为production,将true改为false。这个需求同样可以用lineinfile模块来实现:
- name: set APP_DEBUG=false
lineinfile: dest=/var/www/laravel/.env regexp='^APP_DEBUG=' line=APP_DEBUG=false
- name: set APP_ENV=production
lineinfile: dest=/var/www/laravel/.env regexp='^APP_ENV=' line=APP_ENV=production
打开php.yml进行编辑:
nano php.yml
把上面的代码粘贴进来(handler部分还是在末尾):
...
- name: composer create-project
composer: command=create-project working_dir=/var/www/laravel optimize_autoloader=no
sudo: yes
sudo_user: www-data
when: cloned|changed
- name: set APP_DEBUG=false
lineinfile: dest=/var/www/laravel/.env regexp='^APP_DEBUG=' line=APP_DEBUG=false
- name: set APP_ENV=production
lineinfile: dest=/var/www/laravel/.env regexp='^APP_ENV=' line=APP_ENV=production
handlers:
- name: restart php5-fpm
service: name=php5-fpm state=restarted
- name: restart nginx
service: name=nginx state=restarted
保存退出,运行之:
ansible-playbook php.yml --ask-sudo-pass
lineinfile模块很适合做这种快速修改文本文件的工作,设置环境变量这种工作用它来做再好不过。
步骤7:设置Nginx
本章将配置Nginx用于服务我们的PHP应用。
现在如果你在浏览器里访问你的云主机IP http://your_server_ip/ ,你看到的是Nginx的默认页面,而不是我们的Laravel项目页面。这是因为我们的Nginx还没有被设置为从/var/www/laravel/public目录服务内容。这则设置需要更改Nginx的默认配置文件。另外,我们还需要添加php-fpm以让Nginx能够处理PHP脚本。
创建一个nginx.conf文件:
nano nginx.conf
把下面这些代码粘贴进去。以下内容设置了Laravel所在的公共目录,定义了Nginx使用的server_name跟我们之前在hosts文件中填写的hostname一致(通过inventory_hostname变量调用)。
nginx.conf
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /var/www/laravel/public;
index index.php index.html index.htm;
server_name {{ inventory_hostname }};
location / {
try_files $uri $uri/ =404;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/laravel/public;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
保存退出。
现在,我们用template模块推送上述配置文件。template模块跟copy模块看起来挺像,但两者之间有个关键的区别:copy在复制文件(一个或多个)的时候不进行任何变动,而template则会在复制文件(一个)的时候把所有的变量填进去。我们刚才在配置文件里用到了{{ inventory_hostname }}变量,所以当我们使用template模块时,该模块会把我们填写在hosts文件里的那个IP地址替换进来。这样就不用在配置文件里面进行硬编码了。
另外,因为我们更改了Nginx的配置,我们需要重启Nginx和php-fpm(编写任务的时候经常需要考虑到此类需求)。我们用notify来实现这一需求。
- name: Configure nginx
template: src=nginx.conf dest=/etc/nginx/sites-available/default
notify:
- restart php5-fpm
- restart nginx
就这样。现在再次打开我们的php.yml文件,开始复制粘贴:
nano php.yml
整个文件内容应该是这个样子的:
php.yml
---
- hosts: php
sudo: yes
tasks:
- 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
- name: ensure php5-fpm cgi.fix_pathinfo=0
lineinfile: dest=/etc/php5/fpm/php.ini regexp='^(.*)cgi.fix_pathinfo=' line=cgi.fix_pathinfo=0
notify:
- restart php5-fpm
- restart nginx
- name: enable php5 mcrypt module
shell: php5enmod mcrypt
args:
creates: /etc/php5/cli/conf.d/20-mcrypt.ini
- name: create /var/www/ directory
file: dest=/var/www/ state=directory owner=www-data group=www-data mode=0700
- name: Clone git repository
git: >
dest=/var/www/laravel
repo=https://github.com/laravel/laravel.git
update=no
sudo: yes
sudo_user: www-data
register: cloned
- name: install composer
shell: curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
args:
creates: /usr/local/bin/composer
- name: composer create-project
composer: command=create-project working_dir=/var/www/laravel optimize_autoloader=no
sudo: yes
sudo_user: www-data
when: cloned|changed
- name: set APP_DEBUG=false
lineinfile: dest=/var/www/laravel/.env regexp='^APP_DEBUG=' line=APP_DEBUG=false
- name: set APP_ENV=production
lineinfile: dest=/var/www/laravel/.env regexp='^APP_ENV=' line=APP_ENV=production
- name: Configure nginx
template: src=nginx.conf dest=/etc/nginx/sites-available/default
notify:
- restart php5-fpm
- restart nginx
handlers:
- name: restart php5-fpm
service: name=php5-fpm state=restarted
- name: restart nginx
service: name=nginx state=restarted
保存退出,运行之:
ansible-playbook php.yml --ask-sudo-pass
跑完后,进入浏览器看看你的云主机IP,应该能看到Laravel的项目页面啦!
总结
本文部署了一个公共repo的PHP应用。这对于学习Ansible的用法是足够了,然而在实际工作中,我们经常会需要与私有的git repo打交道,这会涉及SSH密钥验证的步骤(上述步骤3里提到过这个问题)。
你可以用Ansible来复制你的SSH密钥,放在git clone任务之前:
- name: create /var/www/.ssh/ directory
file: dest=/var/www/.ssh/ state=directory owner=www-data group=www-data mode=0700
- name: copy private ssh key
copy: src=deploykey_rsa dest=/var/www/.ssh/id_rsa owner=www-data group=www-data mode=0600
这样就可以正确的完成验证并部署应用了。
至此,你已经成功的在一台基于Ubuntu的Nginx服务器上部署了一个简单的PHP应用,并使用Composer管理起来所有依赖,而整个过程完全不用手动登入这台云主机!