Article

Deploying Symfony2 on Engineyard Cloud

October 10th, 2013

We manage web applications that we build in-house, so deployment is something we've always done ourselves. Usually with bash scripts run directly on the production servers, but more recently using Capistrano.

This is fine, except that is means that we're installing and provisioning and especially updating our own servers - a task we find more and more unnessecary evil. This article describes my explorations in setting up a new environment using Engineyard Cloud...

To get started, I used this excellent article by Matthew Weier O'Phinney : Deploying PHP Applications on Engine Yard: A How-To

This provides the basic steps needed to succeed, but I found the Engineyard process quite confusing at first (mostly because I wasn't familiar with this level of automatic deployment). So I'll try to separate the two steps involved as clearly as I can. I'm talking about the steps "provisioning" and "deploying".

Provsioning

Provisioning is the actual server installation (OS), plus any and all software that you need to be on your machine. Engineyard has a few default setups, so most of the complicated stuff is abstracted away for you. Enginyard uses the following terminology:

  • application: The code that you want to deploy. It comes from a repository - for simplicity I'll assume you use Github- and you give Engineyard access to the repository with a specific deploy key.
  • environment: a name for your environment, like "preview", "staging" or "production". You can deploy the same application to multiple environments obviously.
  • instance: traditionally a "server". I've tried to keep it simple by only doing "single instance" deploys (database and webserver on the same machine), but the preset "staging" and "production" configurations are already composed of three or five instances respectively.

So the first step in getting your Symfony2 application deployed is setting up a new application, environment and instance.

This instance will be provisioned with the default Chef scripts provided by Engineyard for PHP applications. The scripts are quite complete, but for Symfony2 applications there are a few additions needed.

The provisioning scripts are provided by Engineyard in a github repository. So you can clone and change and upload your own provisioning scripts, pretty neat!

The first deploy on this instance will fail by the way, because the composer post-install scripts will try to clear cache but there is no parameters_prod.yml file yet. Let's fix that.

Engineyard has written a commandline tool to help with this (actually, you can do much more with it). The best part of the tool is that it's called "ey" - presumably short for Engine Yard but it reads like your shouting commands at your servers: "EY! deploy!"  (> ey deploy -e {environment})

Installing the tool (it's a ruby gem - hope you know what that is) and getting the scripts:

>gem install engineyard
>git clone https://github.com/engineyard/ey-cloud-recipes-chef-10.git

For the rest of the provisioning step, I'll assume your commandline is in the root folder of the cloned recipes.

First, add the parameters files, at this location: (you don't need to add both) ~/cookbooks/php/files/default/parameters_dev.yml~/cookbooks/php/files/default/parameters_prod.yml

Next, write a script that will place these files in a location we can reach later on. Remember: we're still only provisioning the server, and not deploying the actual project. This is the script:

/cookbooks/php/recipes/configuration.rb

ey_cloud_report "production_config" do
   message "Production config pushed"
end

directory "/data/your-application-name-here/shared/config" do
    owner node[:owner_name]
    group node[:owner_name]
    mode 0755
    action :create
end

[
    'parameters_dev.yml',
    'parameters_prod.yml',
].each do |config|
    cookbook_file "/data/your-application-name-here/shared/config/#{config}" do
        owner node[:owner_name]
        group node[:owner_name]
        mode 0644
        source "#{config}"
        backup false
        action :create
    end
end

Also, while you're at it, add a correct application environment variable for symfony. Edit this  file:

/cookbooks/php/templates/default/env.custom.erb

; Custom Environment variables should go here
env[SYMFONY_ENV] = prod

This should be all you need to do for provisioning  a Symfony2 project, but we also noticed the fact that PHP was configured without a default timezone. That's a problem, so we decided to add an extra php.ini file to the provisioning scripts. This is done via the same principle.

Add a file in this location: ~cookbooks/php/files/default/php.settings.ini~

date.timezone = "Europe/Amsterdam"

And write a script that will place this ini in the right location(s):

~/cookbooks/php/recipes/ini_settings.rb~

ey_cloud_report "production_config" do
   message "PHP ini settings pushed"
end

[
    'cgi-php5.4',
    'cli-php5.4',
    'fpm-php5.4',
].each do |phpconfig|
    cookbook_file "/etc/php/#{phpconfig}/ext/php.settings.ini" do
        owner "root"
        group "root"
        mode 0644
        source "php.settings.ini"
        backup false
        action :create
    end

    link "/etc/php/#{phpconfig}/ext-active/php.settings.ini" do
      to "/etc/php/#{phpconfig}/ext/php.settings.ini"
    end
end

Finally, we activate all this stuff by adding it to the default recipe file: ~/cookbooks/main/recipes/default.rb~

# Add custom environment variables to PHP
include_recipe "php::environment_variables"

# Add custom PHP ini settings
include_recipe "php::ini_settings"

# Add Symfony2 config files
include_recipe "php::configuration"

Now the fun part: use the engineyard tool to upload and apply the changed provisioning scripts:

"EY! recipies upload!"  (> ey recipes upload -e {environment} ) "EY! recipies apply!"(> ey recipes apply -e {environment} )

You can see if this worked by yelling "EY! logs!" (>ey logs -e {environment})

If all is well, it's time for the second stage, deploying the code!

Deploying

Matthew already did a great job explaining this in the article I mentioned before Deploying PHP Applications on Engine Yard: A How-To, so I won't go into it as much.

At this point, I will assume you have a clone of your project locally, and you have moved into the root folder of that project on the commandline.

Create a new folder called /deploy, and add the following file: /deploy/before_bundle.rb

run! "cp #{config.shared_path}/config/*.yml #{config.release_path}/app/config/"

This file will be executed as part of the deploy scripts, it's a "hook". There are more options to hook into the deploy procedure, but I used the "before_bundle" on purpose to be ahead of the composer install command that Engineyard do by default if your project has a composer.lock file.

All this script does is copy the parameters into the app/config folder of your project. These deploy hooks are also a place where you could:

  • create symlinks to the shared folder we saw before (in the provisioning step). Useful for user-generated content that does not live in your repository.
  • change file permissions for upload or caching folders.
  • generate proxy files for Doctrine2, or do migrations

All this wasn't needed in our case, but we do use assetic so lets add a script to dump the assets, right before the application goes live. An exclamation mark in the command means that if it fails (returns with non-zero exit status) the deploy will also fail and will be rolled back. Without assets, the site won't work so I use "run!"

/deploy/before_symlink.rb

run! "cd #{config.release_path}/ && app/console assetic:dump"
run! "cd #{config.release_path}/ && app/console assets:install"
run! "cd #{config.release_path}/ && app/console cache:clear"

*As you can see, there's assets:install and cache:clear as well. Those could now be removed from composer.json. In fact, if you remove the scripts from composer, you could also remove the before_bundle.rb and put the config copy command in before_symlink.rb as well.

Once your deploy scripts are in order, you can try to deploy:

> ey deploy -e {environement} -r {revision, branch or tag}

You'll see a nice step-by-step output of the deploy process. Now all you need to do is check out the result!

> ey launch -e {environment}

I'm ending this post here, assuming you've succesfully provisioned and deployed your Enginyard cloud instance. I will, however, share some of the tools I've used in troubleshooting to get this working.

Good luck!

Troubleshooting

Use the SSH login. Still one of the  most useful things for me is still to be able to just manually check the system. This is made extremely easy once you add a personal SSH key to Engineyard (I think Github as a super easy how-to on generating keys). To log into your instance, just go:

> ey ssh -e {envoronment}

Provisioning error logs. While uploading and applying the chef provisioning scripts, I made more than a few mistakes. I do not ruby enough... These mistakes are not directly visible, but they are logged and can be viewed from the commandline (> ey logs -e {envoronment}) as well as the Engineyard backend. Go to an environment and scroll down to find the logs link.

This will open a new browser window with the logs in plain text. Make sure you "view source", otherwise you'll have a hard time reading...

Check if deployment hooks are run. Once you add the deploy folder and hooks to your project, they should be run during every deploy. You can verify this in the output of the > ey deploy command.

Ramon de la Fuente

Pointy haired boss