Saturday, August 13, 2016

Deploy Your PHP Application With Rocketeer_part 1


There used to be a time when PHP developers had to use deployment tools that were aimed at general web applications. You can see this in Johannes Schickling's tutorial on deploying Laravel applications with Capistrano, for example. It's a Ruby tool, and you need to write Ruby code. These tools get the job done but have limited integration with PHP applications. This can result in hacky solutions for certain scenarios.

But nowadays, we're blessed with a few deployment tools written in our language that enable deeper integration. One of these tools is Rocketeer, a tool that takes inspiration from Capistrano and the Laravel framework.

Rocketeer is a modern tool that brings a great approach for your deployment needs. That is to run tasks and manage your application across different environments and servers. On top of that, it also has some magic in it, like installing Composer dependencies if it detects a composer.json file. You get sane defaults and automation of common tasks for a modern PHP application. And you have the ability to customise and extend everything.

You could describe it as an SSH task runner that runs client-side. The commands execute on servers through an SSH connection. If you use a shared hosting provider with only FTP access, you're out of luck, unfortunately. What you also need is a remote repository where the tool can fetch your code from. There is support for Git and SVN by default. Need support for another version control system? Write your own implementation with the provided interfaces.

Installation

You can install Rocketeer in two different ways. Either you download the phar file and make it executable or you install it through Composer. I'm a supporter of the latter. Having it as a dependency allows for easy installation when cloning the repository. This can benefit anyone cloning the repository to get them up and running.

Installing with Composer:
  1. $ composer require anahkiasen/rocketeer --dev
I don't recommend that you install it globally. Keeping it on the repository level will ensure that each person deploying is running the same version. What I do recommend is that you add vendor/bin to your PATH. Then you can use the binary by typing rocketeer in your project root.

Ignition

Let's get started! First you bootstrap the directories and files for configuration. You do this by running rocketeer ignite in the root of your project.

When your application ignites, the tool creates a .rocketeer folder in your project. The contents of the directory will look like this:
  1. | .rocketeer
  2. | -- config.php
  3. | -- hooks.php
  4. | -- paths.php
  5. | -- remote.php
  6. | -- scm.php
  7. | -- stages.php
  8. | -- strategies.php
These are all the configuration files you need to start setting up your deployments. Whenever I refer to a configuration file from here on out, it exists in the .rocketeer/ directory.

Remote Folder Structure
It's important to understand how Rocketeer manages its folder structure on the server side, since it's a bit different to a regular setup. It uses a few directories for managing certain aspects of the deployment, so it can be effective at what it does. You specify a path to where you want to deploy your application on your server, and the tool will take care of the rest. That folder will look like this, if you have /var/www/app as your application directory.
  1. | /var/www/app
  2. |  | -- current => /var/www/app/releases/20160103183754
  3. |  | -- releases
  4. |  |  | -- 20160101161243
  5. |  |  |  |-- uploads => /var/www/app/shared/uploads
  6. |  |  | -- 20160103183754
  7. |  |  |  |-- uploads => /var/www/app/shared/uploads
  8. |  | -- shared
  9. |  |  | -- uploads
The most important folder is current, which points to your latest release. That is where your web server's document root should be set to. So what happens when you deploy?
  • The tool creates a time-stamped folder in the releases directory.
  • Completes all tasks to make a ready release.
  • Updates the symbolic link current to the new release.
This process makes your deployment transparent to the user. The switch between releases is almost instant, usually referred to as atomic deployments.

Some data should be persistent between your deployments. This can be file uploads, user sessions and logs, for example. Those files or folders go into the shared directory. The tool creates symbolic links inside each release for the ones you've configured.

Events

Events drive the tool, and all strategies and tasks fire a before and after event when they run. They also provide a special halt event when a task fails. This could be for example dependencies.halt, or deploy.halt for general failure. This allows us to hook into the process where we need to.

The default events that happen during a deployment are:
  • deploy.before: before anything happens.
  • create-release.before: before it creates a new release directory.
  • create-release.after: after creating a new release directory.
  • dependencies.before: before installing or updating dependencies.
  • dependencies.after: after installing or updating dependencies. Perhaps make sure that binaries in your vendor folder are executable.
  • test.before: before running tests.
  • test.after: after running tests.
  • migrate.before: before running database migrations. Maybe you want to do a backup of your database?
  • migrate.after: after running database migrations.
  • deploy.before-symlink: before symlinking the release as our current release.
  • deploy.after: completed. You could notify people that everything was smooth sailing or otherwise.
We can also create our own events that we can fire and listen to. For now, we'll stick with these events provided for us. They will be enough for us right now.

Tasks

At the heart of Rocketeer, we find a concept called tasks. Most of what is happening under the hood are core tasks. The definition of a task could be a set of instructions to perform as a step in a deployment. If we look at some of the classes that the tool provides, we can get a general feel of what tasks are: classes such as DeploySetupMigrateRollback, and Dependencies. When you deploy, the deploy command itself is a task with sub-tasks.

Types of Tasks
This is where you'll start to see how integrated the tool is with PHP, since you'll write tasks in the language. You can create your own tasks in three different ways:

Arbitrary terminal commands. These are one-liners that you want to run on your server. Can be useful for a lot of things, like running gulp build  ---production for example.

Closures. If you need a bit more flexibility or complexity, you can write a task as a closure (anonymous function). Say you'd like to generate documentation for an API during your deployment.
  1. function($task) {
  2.     return $task->runForCurrentRelease('apigen generate  source src  destination api');
  3. }
Classes. For more complex tasks, you should utilise the option to create classes for tasks. You create a class and extend Rocketeer\Abstracts\AbstractTask. Then you must provide at least a description and an execute() method. Here's a completely useless example just to show the structure of a task class:
  1. namespace MyDeployableApp\Deploy\Tasks;
  2.  
  3. class HelloWorld extends \Rocketeer\Abstracts\AbstractTask
  4. {
  5.     /**
  6.      * Description of the Task
  7.      *
  8.      * @var string
  9.      */
  10.     protected $description = 'Says hello to the world';
  11.  
  12.     /**
  13.      * Executes the Task
  14.      *
  15.      * @return void
  16.      */
  17.     public function execute() {
  18.         $this->explainer->line('Hello world!');
  19.  
  20.         return true;
  21.     }
  22. }
Note that you have to register task classes yourself. Either you do this through the hooks.php file and add it to the custom array...
  1. 'custom' => array('MyDeployableApp\Deploy\Tasks\HelloWorld',),
... or you can do this through the facade:
  1. Rocketeer::add('MyDeployableApp\Deploy\Tasks\HelloWorld');
Once you register it, you can execute it:
  1. $ rocketeer hello:world
  2. staging/0 | HelloWorld (Says hello to the world)
  3. staging/0 |=> Hello world!
  4. Execution time: 0.004s
Defining Tasks

We discussed events first because we hook tasks in where we need them in the process. You can do this in a few ways. Go with the one you like and that meets requirements for your level of complexity.

The easiest way of defining your tasks is in the hooks.php file. It provides two arrays for this, specifying task execution before or after certain events.
  1. 'before' => [
  2.     'setup' => [],
  3.     'deploy' => ['hello:world'],
  4.     'cleanup' => [],
  5. ],
Strategies

You might be able to tell already that the tasks provided are quite generic. Take Dependencies, for example. What kind of dependencies are we talking about and which package manager?

This is where strategies come into play. A strategy is a specific implementation of a task, such as running tests with Behat or using Gulp for building your front-end. Tasks have a default strategy with the option of running the other strategies through the CLI. We can list the strategies available like this:


Creating Your Own Strategies

Say you're doing BDD With Behat for your application instead of TDD. Then you want to run your tests with Behat instead of PHPUnit. Since it is a test runner, there is already a strategy namespace for that, but no implementation. Create the directory .rocketeer/strategies/ and place your new BehatStrategy.php in there.
  1. namespace MyDeployableApp\Deploy\Strategies;
  2.  
  3. use Rocketeer\Abstracts\Strategies\AbstractStrategy;use Rocketeer\Interfaces\Strategies\TestStrategyInterface;
  4.  
  5. class BehatStrategy extends AbstractStrategy implements TestStrategyInterface
  6. {
  7.     public function test() {
  8.         return $this->binary('vendor/bin/behat')->runForCurrentRelease();
  9.     }
  10. }
You can now switch out your test strategy to the new implementation in strategies.php.
  1. 'test' => 'Behat',
Connections & Stages

It doesn't matter if you have an infrastructure in place or have one in mind. It doesn't matter if your application deploys to many environments across many servers. Rocketeer will be there for you. You can even have many varying locations on the same server. This is where the terms connections and stages enter.

A connection is a server where you deploy your application to. This is often called an environment, and production and staging are examples of this. Configuring these connections is a breeze in the tool. Either you do it through a nested array or by keeping separate files for each connection. Each connection can also have multiple servers in it.

Stages are like connections inside connections, a kind of "connectionception". You could set up a staging and a production environment on a single server with the use of stages. So instead of having two separate connections, you have one connection with two stages in it.

Plugins

A great feature is that we can extend our process using plugins. There are a few official ones for integration with Laravel, Slack, HipChat and Campfire. Then there are a few, but not that many, on Packagist. Installing plugins is an easy task through the CLI:
  1. $ rocketeer plugin:install rocketeers/rocketeer-slack
Even though there's a limited number of plugins, it leaves room for developing plugins in the future. It tells of a good philosophy. And why not develop one of your own?
Written by Niklas Modess

If you found this post interesting, follow and support us.
Suggest for you:

Learn PHP 7 This Way to Rise Above & Beyond Competion!

PHP MySQL Database Connections

The Complete PHP with MySQL Developer Course (New)

Learn what's new in PHP 7

Modern Programming with PHP

No comments:

Post a Comment