Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Elephants in the Clouds: Mastering PHP on Heroku

David Zuelke
February 05, 2015

Elephants in the Clouds: Mastering PHP on Heroku

Tutorial given at SunshinePHP 2015 in Miami, Florida.

David Zuelke

February 05, 2015
Tweet

More Decks by David Zuelke

Other Decks in Programming

Transcript

  1. 4/12 MAIN FACTORS, CODE ACCORDINGLY I. Codebase
 From one codebase,

    perform many deploys to staging, prod, ... II. Dependencies
 Your application explicitly declares userland and platform deps. III. Configuration
 Read from $_ENV: API keys, database credentials, SMTP hosts, ... XI. Logging
 file_put_contents("php://stderr",  "Yay  log");
  2. II. DEPENDENCIES Applications have explicitly declared dependencies. $  cat  composer.json

      {      "require":  {          "php":  ">=5.3.3",          "ext-­‐mcrypt":  "*",          "symfony/symfony":  "~2.4.6",          "twig/extensions":  "~1.0",          "symfony/monolog-­‐bundle":  "~2.4"      }   } $  cat  package.json   {      "dependencies":  {          "express":  "~4.9.x",          "cool-­‐ascii-­‐faces":  "~1.3.x"      },      "engines":  {          "node":  "0.10.x"      }   }
  3. III. CONFIGURATION Store config in the environment. Assumption: same code

    but different configuration per deployment target
  4. III. CONFIGURATION Store config in the environment. $transport  =  Swift_SmtpTransport::newInstance(


           getenv('EMAIL_HOST'),  getenv('EMAIL_PORT')?:25
 )
        -­‐>setUsername(getenv('EMAIL_USERNAME'))
        -­‐>setPassword(getenv('EMAIL_PASSWORD'))
 ; Assumption: same code but different configuration per deployment target $  heroku  config:set  EMAIL_HOST=blah.com  EMAIL_PORT=827   EMAIL_USERNAME=joecool  EMAIL_PASSWORD=itwasadarkandstormynight
  5. $  heroku  create  a-­‐php-­‐app   Creating  a-­‐php-­‐app...  done,  stack  is

     cedar-­‐14   http://a-­‐php-­‐app.herokuapp.com/  |  [email protected]:a-­‐php-­‐app.git   $  git  push  heroku  master   -­‐-­‐-­‐-­‐-­‐>  PHP  app  detected   -­‐-­‐-­‐-­‐-­‐>  Setting  up  runtime  environment...                -­‐  PHP  5.5.16                -­‐  Apache  2.4.10                -­‐  Nginx  1.6.0   -­‐-­‐-­‐-­‐-­‐>  Installing  PHP  extensions:                -­‐  opcache  (automatic;  bundled)                -­‐  memcached  (composer.json;  downloaded)                -­‐  intl  (composer.json;  bundled)   -­‐-­‐-­‐-­‐-­‐>  Installing  dependencies...                Composer  version  05d991  2014-­‐10-­‐28  12:36:19                Loading  composer  repositories  with  package  information                Installing  dependencies  from  lock  file                    -­‐  Installing  monolog/monolog  (1.9.1)                        Loading  from  cache                          Generating  optimized  autoload  files   -­‐-­‐-­‐-­‐-­‐>  Discovering  process  types                Procfile  declares  types  -­‐>  web   -­‐-­‐-­‐-­‐-­‐>  Compressing...  done,  57.4MB   -­‐-­‐-­‐-­‐-­‐>  Launching...  done,  v3                http://a-­‐php-­‐app.herokuapp.com/  deployed  to  Heroku
  6. V. BUILD, RELEASE, RUN A build step vendors dependencies, prepares

    assets, etc. A release step creates a package from build and config. A runtime step executes, without special knowledge.
  7. V. BUILD, RELEASE, RUN A build step vendors dependencies, prepares

    assets, etc. A release step creates a package from build and config. A runtime step executes, without special knowledge.
  8. PREREQUISITES • Git installed on your system • A Heroku

    account, created via http://signup.heroku.com/ • The Heroku toolbelt installed from http://toolbelt.heroku.com/ • $  heroku  login
  9. $  mkdir  php-­‐heroku   $  cd  php-­‐heroku   $  git

     init   Initialized  empty  Git  repository  in  php-­‐heroku/.git/   $  curl  -­‐sS  https://getcomposer.org/installer  |  php   $  php  composer.phar  require  silex/silex  monolog/monolog   ...   ./composer.json  has  been  created   Loading  composer  repositories  with  package  information   Updating  dependencies  (including  require-­‐dev)   ...   $  echo  "/vendor/"  >  .gitignore   $  echo  "hi"  >  index.php   $  git  add  .gitignore  composer.*  index.php   $  git  commit  -­‐m  "first  rough  version"   [master  (root-­‐commit)  82b60dd]  first  rough  version    5  files  changed,  498  insertions(+)   ...   $  heroku  create  sunshinephp-­‐$(whoami)   Creating  sunshinephp-­‐dzuelke...  done,  stack  is  cedar-­‐14   https://sunshinephp-­‐dzuelke.herokuapp.com/  |   [email protected]:sunshinephp-­‐dzuelke.git   Git  remote  heroku  added   $  git  push  heroku  master
  10. X. DEV/PROD PARITY Keep dev, stage and prod envs as

    similar as possible. SQLite ≠ MySQL Apache ≠ Nginx File based sessions ≠ Redis based sessions
  11. X. DEV/PROD PARITY Keep dev, stage and prod envs as

    similar as possible. SQLite ≠ MySQL Apache ≠ Nginx File based sessions ≠ Redis based sessions
  12. X. DEV/PROD PARITY Keep dev, stage and prod envs as

    similar as possible. SQLite ≠ MySQL Apache ≠ Nginx File based sessions ≠ Redis based sessions If apt-­‐get or brew don't get the job done on your box: Vagrant is always your friend!
  13. heroku-­‐ruby-­‐app  $  cat  Procfile   worker:  env  TERM_CHILD=1  bundle  exec

     rake  resque:work   web:  bundle  exec  unicorn  -­‐p  $PORT  -­‐c  ./config/unicorn.rb
  14. heroku-­‐java-­‐app  $  cat  Procfile   worker:  sh  worker/target/bin/worker   web:

     java  -­‐jar  target/dependency/jetty-­‐runner.jar  -­‐-­‐port  $PORT   target/*.war
  15. heroku-­‐php-­‐app  $  cat  Procfile   worker:  php  background.php   web:

     vendor/bin/heroku-­‐php-­‐apache2  #  or  heroku-­‐php-­‐nginx automatically injected into the app during build
  16. RUNNING PHP APPS LOCALLY heroku-­‐php  $  composer  require  -­‐-­‐dev  heroku/heroku-­‐buildpack-­‐php

      ./composer.json  has  been  updated   Loading  composer  repositories  with  package  information   Updating  dependencies  (including  require-­‐dev)      -­‐  Installing  heroku/heroku-­‐buildpack-­‐php  (v43)          Loading  from  cache   Writing  lock  file   Generating  autoload  files   heroku-­‐php  $  foreman  start   14:30:45  web.1  |  started  with  pid  11175   14:30:46  web.1  |  Booting  on  port  5000...   14:30:46  web.1  |  Starting  php-­‐fpm...   14:30:46  web.1  |  Starting  nginx...
  17. $  mkdir  web   $  git  mv  index.php  web/  

    $  echo  "web:  vendor/bin/heroku-­‐php-­‐nginx  web/"  >  Procfile   $  git  add  Procfile   $  git  commit  -­‐m  "use  a  custom  document  root"   [master  e87d03e]  use  a  custom  document  root    2  files  changed,  1  insertion(+)    create  mode  100644  Procfile    rename  index.php  =>  web/index.php  (100%)   $  echo  '<?php   require_once  __DIR__."/../vendor/autoload.php";   $app  =  new  Silex\Application();   $app-­‐>get("/",  function  ()  use  ($app)  {          return  "Hello  World!";   });   $app-­‐>run();'  >  web/index.php   $  git  add  web/index.php   $  git  commit  -­‐m  "use  Silex"   [master  fe86acb]  use  Silex    1  file  changed,  10  insertions(+),  1  deletion(-­‐)   $  git  push  heroku  master
  18. $  vi  web/index.php  #  or  whatever  your  editor  is  

    $  git  diff  #  edit  the  file  like  in  this  diff:   diff  -­‐-­‐git  a/web/index.php  b/web/index.php   index  4272703..02b453d  100644   -­‐-­‐-­‐  a/web/index.php   +++  b/web/index.php   @@  -­‐3,7  +3,12  @@  require_once  __DIR__."/../vendor/ autoload.php";        $app  =  new  Silex\Application();       +$app-­‐>register(new  Silex\Provider\MonologServiceProvider(),   array(   +        "monolog.logfile"  =>  "php://stderr",   +));   +    $app-­‐>get("/",  function  ()  use  ($app)  {   +        $app["monolog"]-­‐>addNotice("The  world  is  not  enough!");            return  "Hello  World!";    });   $  git  add  web/index.php   $  git  commit  -­‐m  "log  something"   [master  72c4ea3]  log  something    1  file  changed,  5  insertions(+)   $  git  push  heroku  master
  19. $  heroku  logs  -­‐-­‐tail   2014-­‐11-­‐06T06:35:07.223192+00:00  app[web.1]:      

       [2014-­‐11-­‐06  06:35:07]  myapp.INFO:              Matched  route  "GET_"              (parameters:  "_controller":  "{}",  "_route":  "GET_")  []  []   2014-­‐11-­‐06T06:35:07.223499+00:00  app[web.1]:          [2014-­‐11-­‐06  06:35:07]  myapp.INFO:  >  GET  /  []  []   2014-­‐11-­‐06T06:35:07.224280+00:00  app[web.1]:          [2014-­‐11-­‐06  06:35:07]  myapp.NOTICE:              The  world  is  not  enough!  []  []   2014-­‐11-­‐06T06:35:07.225503+00:00  app[web.1]:          [2014-­‐11-­‐06  06:35:07]  myapp.INFO:  <  200  []  []   2014-­‐11-­‐06T06:35:07.226102+00:00  app[web.1]:          10.12.50.108  -­‐  -­‐  [06/Nov/2014:06:35:07  +0000]          "GET  /  HTTP/1.1"  200  12  "-­‐"          "Mozilla/5.0  (Macintosh;  Intel  Mac  OS  X  10_9_5)  AppleWebKit          /600.1.17  (KHTML,  like  Gecko)  Version/7.1  Safari/537.85.10"   2014-­‐11-­‐06T06:35:07.230159+00:00  heroku[router]:          at=info  method=GET  path="/"          host=blooming-­‐spire-­‐8023.herokuapp.com          request_id=57502c87-­‐4b60-­‐472e-­‐8aa2-­‐3e1977f8a086          fwd="199.36.244.11"          dyno=web.1  connect=1ms  service=18ms  status=200  bytes=245
  20. $  heroku  addons:add  heroku-­‐postgresql   Adding  heroku-­‐postgresql  on  tnphptest...  done,

     v6  (free)   Attached  as  HEROKU_POSTGRESQL_AQUA_URL   Use  `heroku  addons:docs  heroku-­‐postgresql`  to  view  documentati…   $  heroku  addons:add  heroku-­‐postgresql   Adding  heroku-­‐postgresql  on  tnphptest...  done,  v7  (free)   Attached  as  HEROKU_POSTGRESQL_AMBER_URL   Use  `heroku  addons:docs  heroku-­‐postgresql`  to  view  documentati…   $  heroku  addons:add  newrelic   Adding  newrelic  on  tnphptest...  done,  v8  (free)   Use  `heroku  addons:docs  newrelic`  to  view  documentation.   $  heroku  addons:add  papertrail   Adding  papertrail  on  tnphptest...  done,  v9  (free)   Use  `heroku  addons:docs  papertrail`  to  view  documentation.   $  heroku  config   ===  tnphptest  Config  Vars   DATABASE_URL:                                postgres://u:p@host12:5432/db18172   HEROKU_POSTGRESQL_AMBER_URL:  postgres://u:p@host42:5198/db24438   HEROKU_POSTGRESQL_AQUA_URL:    postgres://u:p@host12:5432/db18172   NEW_RELIC_LICENSE_KEY:              c9eecdcf9523862f981e   PAPERTRAIL_API_TOKEN:                13Ii1sjmchTboDS24I
  21. SCALING $  heroku  ps:scale  web=10   Scaling  dynos...  done,  now

     running  web  at  10:1X.   $  heroku  ps   ===  web  (1X):  `vendor/bin/heroku-­‐php-­‐apache2  web/`   web.1:  starting  2014/11/05  20:36:39  (~  4s  ago)   web.2:  starting  2014/11/05  20:36:39  (~  4s  ago)   web.3:  starting  2014/11/05  20:36:39  (~  4s  ago)   web.4:  starting  2014/11/05  20:36:38  (~  4s  ago)   web.5:  starting  2014/11/05  20:36:38  (~  4s  ago)   web.6:  starting  2014/11/05  20:36:39  (~  4s  ago)   web.7:  starting  2014/11/05  20:36:38  (~  4s  ago)   web.8:  starting  2014/11/05  20:36:39  (~  4s  ago)   web.9:  starting  2014/11/05  20:36:38  (~  4s  ago)   web.10:  starting  2014/11/05  20:36:39  (~  4s  ago)   $  heroku  ps:scale  web=1   Scaling  dynos...  done,  now  running  web  at  1:1X.
  22. XII. ADMIN PROCESSES Management tasks like DB migrations are one-off

    processes. They run in isolation, just like all other "dynos". With the same release: same code, same config!
  23. XII. ADMIN PROCESSES Management tasks like DB migrations are one-off

    processes. $  heroku  run  "php  app/console  doctrine:migrations:migrate"   Running  `php  app/console…`  attached  to  terminal...  up,  run.4062   Migrating  up  to  20100416130452  from  0      >>  migrating  20100416130452            -­‐>  CREATE  TABLE  users  (username  VARCHAR(255)  NOT  NULL,                  password  VARCHAR(255)  NOT  NULL)  ENGINE  =  InnoDB      >>  migrated They run in isolation, just like all other "dynos". With the same release: same code, same config!
  24. PIPELINES $  heroku  labs:enable  pipelines   $  heroku  plugins:install  git://github.com/heroku/heroku-­‐pipeline

      $  heroku  pipeline:add  your-­‐app-­‐production  -­‐-­‐app  your-­‐app-­‐staging   Pipeline:  your-­‐app-­‐staging  -­‐-­‐-­‐>  your-­‐app-­‐production   $  heroku  pipeline:diff   Comparing  your-­‐app-­‐staging  to  your-­‐app-­‐production...done,    your-­‐app-­‐staging  ahead  by  1  commit:   73ab415    2014-­‐11-­‐05    Fix  checkout  for  IE  users    (Joe)   $  heroku  pipeline:promote   Promoting  myapp-­‐staging  to  myapp-­‐production...done,  v817
  25. FORK Clone a database using a single command. $  heroku

     addons:add  heroku-­‐postgresql:standard-­‐0  \      -­‐-­‐fork  HEROKU_POSTGRESQL_CHARCOAL_URL   Adding  heroku-­‐postgresql:standard-­‐0  on  app...  done,  v7  ($50/mo)   Attached  as  HEROKU_POSTGRESQL_SILVER_URL   Database  will  become  available  after  it  completes  forking   Use  `heroku  pg:wait`  to  track  status   $  heroku  pg:wait   Waiting  for  database  HEROKU_POSTGRESQL_SILVER_URL...  available
  26. FOLLOW Horizontal scalability using read-only replicas. $  heroku  addons:add  heroku-­‐postgresql:standard-­‐0

     \      -­‐-­‐follow  HEROKU_POSTGRESQL_CHARCOAL_URL   Adding  heroku-­‐postgresql:standard-­‐0  to  foo...  done,  v8  ($50/mo)   Attached  as  HEROKU_POSTGRESQL_RED   $  heroku  pg:unfollow  HEROKU_POSTGRESQL_WHITE_URL   !        HEROKU_POSTGRESQL_WHITE  will  become  writable  and  no  longer   !        follow  HEROKU_POSTGRESQL_CHARCOAL.  This  cannot  be  undone.   !        WARNING:  Potentially  Destructive  Action   !        This  command  will  affect  the  app:  foo   !        To  proceed,  type  "foo"  or  re-­‐run  this  command  with  -­‐-­‐ confirm  foo   >  foo   Unfollowing...  done
  27. ROLLBACK Jump back in time like that litte disaster never

    happened. $  heroku  addons:add  heroku-­‐postgresql:standard-­‐0  \      -­‐-­‐rollback  HEROKU_POSTGRESQL_RED_URL  -­‐-­‐by  '1  hour'   Adding  heroku-­‐postgresql:standard-­‐0  on  foo...  done,  v9  ($50/mo)   Attached  as  HEROKU_POSTGRESQL_YELLOW_URL   Database  will  become  available  after  it  completes  rolling  back   to  2015-­‐01-­‐16  09:02:14  +0000  (01:00:00  ago)   Use  `heroku  pg:wait`  to  track  status.   Use  `heroku  addons:docs  heroku-­‐postgresql`  to  view   documentation.
  28. heroku-­‐php  $  git  rm  Procfile   heroku-­‐php  $  composer  require

     -­‐-­‐ignore-­‐platform-­‐reqs  hhvm  ~3.2   ./composer.json  has  been  updated   Loading  composer  repositories  with  package  information   Updating  dependencies  (including  require-­‐dev)   Nothing  to  install  or  update   Generating  autoload  files   heroku-­‐php  $  git  add  composer.*   heroku-­‐php  $  git  commit  -­‐m  'use  HHVM'   heroku-­‐php  $  git  push  heroku  master   -­‐-­‐-­‐-­‐-­‐>  PHP  app  detected   -­‐-­‐-­‐-­‐-­‐>  Detected  request  for  HHVM  3.4.2  in  composer.lock.   -­‐-­‐-­‐-­‐-­‐>  Setting  up  runtime  environment...                -­‐  HHVM  3.2.0                -­‐  Apache  2.4.10                -­‐  Nginx  1.6.0   -­‐-­‐-­‐-­‐-­‐>  Building  runtime  environment...                NOTICE:  No  Procfile,  defaulting  to  'web:  vendor/bin/ heroku-­‐hhvm-­‐apache2'   -­‐-­‐-­‐-­‐-­‐>  Compressing...  done,  77.4MB   -­‐-­‐-­‐-­‐-­‐>  Launching...  done,  v3                http://your-­‐hhvm-­‐app.herokuapp.com/  deployed  to  Heroku