DevOps in PHP environment
1
Who?!
Gabriel Koerich
Over 10 years of experience in PHP and 6 in Laravel
Founder of Bulldesk, responsible for finance and technology
gabriel@bulldesk.com.br
twi3er.com/gabrielmkoerich
github.com/gabrielkoerich
2
3
Who?!
Evaldo Felipe
Blackops @ Neoway
8 years of SysAdmin
2,5 years of DevOps
contato@evaldofelipe.com
twi1er.com/evaldofelipe
4
5
Agenda
• PHP Sucks?!
• Workflow
• Infrastructure
• Tools
• Issues and Fixes
6
PHP Sucks?!
7
PHP Sucks?!
• Easy for beginners
• Easy to "deploy"
• PHP mixed in HTML
• No conven>ons for func>on names
• Wordpress
8
I don't know how to stop it, there
was never any intent to write a
programming language
— Rasmus Lerdorf
9
PHP Evolu)on
Version Evolu,on
< 5.3 !
5.3 Namespaces, closures
5.4 Traits, [] for arrays
5.5 OPCache, finally on try blocks
5.6 Argument unpacking (...$args)
6.0 "
7.0 Performance (thanks to HHVM), return and scalar types,
improved excepPons
7.1 Nullable types (?int), catch mulPple excepPons
10
I have absolutely no idea how to
write a programming language, I just
kept adding the next logical step on
the way.
— Rasmus Lerdorf
11
Community Evolu-on
Autoloaders, Composer & Packagist
Frameworks (Zend, Symfony, Laravel)
PHP FIG and PSRs
Standards
PSR-1: Basic Coding
PSR-2: Coding Style Guide
PSR-3: Logger Interface
PSR-4: Autoloader
PSR-5: Caching Interface
12
Modern PHP
/**
* Create a new instance.
*/
public function __construct(AutomationRepository $automations)
{
$this->automations = $automations;
}
/**
* @Get("automation", as="automation.index")
*/
public function index(Request $request): Response
{
if ($request->expectsJson())) {
return $this->automations->getActive()->pluck('name', 'id');
}
$automations = $this->automations->categorized($request->input('categories'))
->orderBy('name')
->paginate(50);
return new Response(view('pages.automation.index')->with(compact('automations')), 200);
}
13
Laravel Features
• Routes & Controllers
• Service Container & Dependency Injec9on
• Migra9ons & ORM
• Ar9san command line
• Queues (beanstalkd, redis, amazon sqs)
• Broadcas9ng (Pusher or socket.io)
14
Development Workflow &
Source Management
15
Development Workflow & Source
Management
• People
• Workflow
• Rules, code pa4erns, PSRs
• Local environment
• Database dump Migra@ons / Seeds
16
Github Flow
• Anything in master is deployable
• Every new branch should be created off of master
• Branches must have descrip<ve names (create-cache-manager,
improve-auth, refactor-acl)
• Pull requests must be reviewed by at least 2 people
• When ready, you should merge and deploy immediately
17
Github Flow
18
Bulldesk Guidelines
19
20
Local Environment
21
Laravel Valet
# Install PHP/Composer/MySQL/Redis local
# On MacOS
$ composer global require laravel/valet
# On Linux
$ composer global require cpriego/valet-linux
$ valet install
$ cd ~/Projects && valet park
# All directories in ~/Projects will be acessible at http://{folder}.test
22
Vessel (Docker)
PHP 7.2, MySQL 5.7, Redis & NodeJS with NPM, Yarn &
Gulp
# Install docker
$ composer require shipping-docker/vessel
# Register VesselVesselServiceProvide if not on Laravel 5.5
$ php artisan vendor:publish --provider="VesselVesselServiceProvider"
$ bash vessel init
$ ./vessel start
# Acessible at http://localhost
23
Con$nuous Integra$on
24
Con$nuous Integra$on
• Tests
• Code coverage
• Code quality
25
26
27
28
Produc'on/QA Environment
29
Provision/Deploy
• Mul% Servers / Instances
• Con%nuous Deploy
• Zero down%me
• Database Replica%on
• PHP 7.2
• SSL & hFp2
30
31
32
33
Recipes
34
35
Firewall Rules
ACCEPT tcp -- 10.132.103.204 anywhere tcp dpt:mysql
ACCEPT udp -- 10.132.103.204 anywhere udp dpt:mysql
ACCEPT tcp -- 10.132.103.204 anywhere tcp dpt:postgresql
ACCEPT udp -- 10.132.103.204 anywhere udp dpt:postgresql
ACCEPT tcp -- 10.132.103.204 anywhere tcp dpt:11211
ACCEPT udp -- 10.132.103.204 anywhere udp dpt:11211
ACCEPT tcp -- 10.132.103.204 anywhere tcp dpt:6379
ACCEPT udp -- 10.132.103.204 anywhere udp dpt:6379
ACCEPT tcp -- 10.132.103.204 anywhere tcp dpt:11300
ACCEPT udp -- 10.132.103.204 anywhere udp dpt:11300
36
37
38
Beanstalkd queues + Supervisor
39
40
Issue
Laravel doesn't understand that the request
came from a client and not from the load
balancer
41
Trusted Proxy
$ composer require fideloper/proxy
# Register FideloperProxyTrustedProxyServiceProvider
$ php artisan vendor:publish --provider="FideloperProxyTrustedProxyServiceProvider"
// Http/Kernel.php
protected $middleware = [
//...
FideloperProxyTrustProxies::class,
];
// config/trustedproxy.php
return [
'proxies' => [
'192.168.10.10', // Load balancer's IP address
],
//...
42
Issue
How to serve some files from a single server?
43
Nginx Websocket Proxy
upstream websocket {
least_conn;
server 10.xx.xx.xx:2095; #websocket server ip
}
location /socket.io {
proxy_pass http://websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
send_timeout 300;
}
44
Database Replica-on
45
Master/Slave Config
//...
'connections' => [
'mysql' => [
'write' => [
'host' => env('DB_WRITE_HOST', env('DB_HOST', '127.0.0.1')),
],
'read' => [
'host' => [
env('DB_WRITE_HOST', env('DB_HOST', '127.0.0.1')),
env('DB_READ_HOST', env('DB_HOST', '127.0.0.1')),
],
],
'backup' => [
'host' => env('DB_BACKUP_HOST', env('DB_READ_HOST', '127.0.0.1')),
'arguments' => '--single-transaction --skip-tz-utc',
],
//...
];
46
47
48
49
50
.env
REDIS_HOST=
BEANSTALKD_HOST=
DB_HOST=
DB_WRITE_HOST=
DB_READ_HOST=
DB_BACKUP_HOST=
DB_NAME=
DB_USER=
DB_PASSWORD=
CACHE_DRIVER=redis
SESSION_DRIVER=redis
51
52
53
54
55
56
57
Monitoring, Security & Op2miza2on
58
Monitoring, Security & Op2miza2on
• CDN
• Monitors
• Logs e excep3ons centralized
• Backups
• Load tests
59
60
61
/**
* Set the CloudFlare conneting IP and https if applied.
*
* @param IlluminateHttpRequest $request
* @param Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (! app()->environment('production')) {
return $next($request);
}
if ($request->server->has('HTTP_CF_CONNECTING_IP')) {
if ($request->isSecure()) {
$request->server->set('HTTPS', true);
}
$request->server->set('REMOTE_ADDR', $request->server->get('HTTP_CF_CONNECTING_IP'));
return $next($request);
}
logf('CF: Blocked request to %s from %s', [$request->fullUrl(), $request->ip()]);
return new Response('', 404);
}
62
63
Issue
How to clear this cache automa0cally?
64
/**
* Get the path to a versioned asset file.
*
* @param string $asset
* @return string
*/
function versioned($asset)
{
static $production, $base, $deploy, $sha;
$production = app()->environment('production');
if (is_null($base)) {
$base = $production ? 'https://static.bulldesk.com.br/' : '/';
}
if ($production === false) {
return $base.$asset . '?v=' . str_random(3);
}
if (is_null($deploy)) {
$deploy = public_path('build/deploy');
}
if (is_null($sha) && file_exists($deploy)) {
$sha = file_get_contents($deploy);
}
if (! empty($sha)) {
return $base.$asset . '?v='.$sha;
}
return $base.$asset;
}
<script src="{{ versioned('build/scripts/app.js') }}"></script> //https://static.bulldesk.com.br/build/scripts/app.js?v=fbe06e04c4f8ddd1c94c63f3a001807bace679e0
65
Monitoring
66
67
68
69
70
71
Issue
How to centralize logs and excep3ons?
72
73
74
// LogServiceProvider.php
/**
* The papertrail log format.
*
* @var string
*/
protected $papertrailFormat = '%channel%.%level_name%: %message% %extra%';
/**
* Configure the application's logging facilities.
*
* @return void
*/
public function boot()
{
if ($this->app->environment('production', 'staging')) {
$syslog = new MonologHandlerSyslogHandler('laravel');
$formatter = new MonologFormatterLineFormatter($this->papertrailFormat);
$syslog->setFormatter($formatter);
$this->app['log']->getMonolog()->pushHandler($syslog);
}
}
75
76
77
78
79
80
Backups
$ composer require backup-manager/backup-manager
# OR
$ composer require backup-manager/laravel
# OR
$ composer require spatie/laravel-backup
81
(...)
/**
* Execute the backup command.
*
* @return void
*/
public function handle()
{
$database = $this->option('database') ?: 'mysql';
$destination = $this->option('destination') ?: 's3';
$destinationPath = 'dump_'. (new Carbon)->format('Y_m_d_H_i_s').'.sql';
$compression = 'gzip';
$this->info('Dumping database and uploading...');
$destinations = [new Destination($destination, $destinationPath)];
$this->backupProcedure->run($database, $destinations, $compression);
$completePath = 'database/' . $destinationPath;
$this->info(sprintf('Successfully dumped %s, compressed with %s and store it to %s at %s',
$database,
$compression,
$destination,
$completePath
));
$last = new Carbon('last day of this month');
// Delete file in 10 days if this not the last
if ((new Carbon)->isSameAs('d/m/Y', $last)) {
$this->info('Scheduled job to delete the backup file ' . $completePath . ' in 10 days.');
dispatch((new DeleteFile($completePath . '.gz', 's3_backup'))->delay(24 * 60 * 60 * 10));
}
}
82
Then
83
Now
84
Lessons Learned
85
1. Don't op*mize or escale
prematurely, but be prepared for
this.
86
2. Time is money.
87
3. Use microservices only if you can
maintain them.
88
4. Study/learn and enjoy everything
the internet gives you for free.
89
90
91
Thanks!
92
We're hiring!
Ques%ons?
bulldesk.com.br
gabriel@bulldesk.com.br
twi3er.com/gabrielmkoerich
93
Ref
en.wikiquote.org/wiki/Rasmus_Lerdorf
guides.github.com/introduc9on/flow
github.com/gabrielkoerich/guidelines
vessel.shippingdocker.com
docs.spa9e.be/laravel-backup
gabrielkoerich.com/talks/infra
php-fig.org
94

DevOps in PHP environment

  • 1.
    DevOps in PHPenvironment 1
  • 2.
    Who?! Gabriel Koerich Over 10years of experience in PHP and 6 in Laravel Founder of Bulldesk, responsible for finance and technology gabriel@bulldesk.com.br twi3er.com/gabrielmkoerich github.com/gabrielkoerich 2
  • 3.
  • 4.
    Who?! Evaldo Felipe Blackops @Neoway 8 years of SysAdmin 2,5 years of DevOps contato@evaldofelipe.com twi1er.com/evaldofelipe 4
  • 5.
  • 6.
    Agenda • PHP Sucks?! •Workflow • Infrastructure • Tools • Issues and Fixes 6
  • 7.
  • 8.
    PHP Sucks?! • Easyfor beginners • Easy to "deploy" • PHP mixed in HTML • No conven>ons for func>on names • Wordpress 8
  • 9.
    I don't knowhow to stop it, there was never any intent to write a programming language — Rasmus Lerdorf 9
  • 10.
    PHP Evolu)on Version Evolu,on <5.3 ! 5.3 Namespaces, closures 5.4 Traits, [] for arrays 5.5 OPCache, finally on try blocks 5.6 Argument unpacking (...$args) 6.0 " 7.0 Performance (thanks to HHVM), return and scalar types, improved excepPons 7.1 Nullable types (?int), catch mulPple excepPons 10
  • 11.
    I have absolutelyno idea how to write a programming language, I just kept adding the next logical step on the way. — Rasmus Lerdorf 11
  • 12.
    Community Evolu-on Autoloaders, Composer& Packagist Frameworks (Zend, Symfony, Laravel) PHP FIG and PSRs Standards PSR-1: Basic Coding PSR-2: Coding Style Guide PSR-3: Logger Interface PSR-4: Autoloader PSR-5: Caching Interface 12
  • 13.
    Modern PHP /** * Createa new instance. */ public function __construct(AutomationRepository $automations) { $this->automations = $automations; } /** * @Get("automation", as="automation.index") */ public function index(Request $request): Response { if ($request->expectsJson())) { return $this->automations->getActive()->pluck('name', 'id'); } $automations = $this->automations->categorized($request->input('categories')) ->orderBy('name') ->paginate(50); return new Response(view('pages.automation.index')->with(compact('automations')), 200); } 13
  • 14.
    Laravel Features • Routes& Controllers • Service Container & Dependency Injec9on • Migra9ons & ORM • Ar9san command line • Queues (beanstalkd, redis, amazon sqs) • Broadcas9ng (Pusher or socket.io) 14
  • 15.
  • 16.
    Development Workflow &Source Management • People • Workflow • Rules, code pa4erns, PSRs • Local environment • Database dump Migra@ons / Seeds 16
  • 17.
    Github Flow • Anythingin master is deployable • Every new branch should be created off of master • Branches must have descrip<ve names (create-cache-manager, improve-auth, refactor-acl) • Pull requests must be reviewed by at least 2 people • When ready, you should merge and deploy immediately 17
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
    Laravel Valet # InstallPHP/Composer/MySQL/Redis local # On MacOS $ composer global require laravel/valet # On Linux $ composer global require cpriego/valet-linux $ valet install $ cd ~/Projects && valet park # All directories in ~/Projects will be acessible at http://{folder}.test 22
  • 23.
    Vessel (Docker) PHP 7.2,MySQL 5.7, Redis & NodeJS with NPM, Yarn & Gulp # Install docker $ composer require shipping-docker/vessel # Register VesselVesselServiceProvide if not on Laravel 5.5 $ php artisan vendor:publish --provider="VesselVesselServiceProvider" $ bash vessel init $ ./vessel start # Acessible at http://localhost 23
  • 24.
  • 25.
    Con$nuous Integra$on • Tests •Code coverage • Code quality 25
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
    Provision/Deploy • Mul% Servers/ Instances • Con%nuous Deploy • Zero down%me • Database Replica%on • PHP 7.2 • SSL & hFp2 30
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
    Firewall Rules ACCEPT tcp-- 10.132.103.204 anywhere tcp dpt:mysql ACCEPT udp -- 10.132.103.204 anywhere udp dpt:mysql ACCEPT tcp -- 10.132.103.204 anywhere tcp dpt:postgresql ACCEPT udp -- 10.132.103.204 anywhere udp dpt:postgresql ACCEPT tcp -- 10.132.103.204 anywhere tcp dpt:11211 ACCEPT udp -- 10.132.103.204 anywhere udp dpt:11211 ACCEPT tcp -- 10.132.103.204 anywhere tcp dpt:6379 ACCEPT udp -- 10.132.103.204 anywhere udp dpt:6379 ACCEPT tcp -- 10.132.103.204 anywhere tcp dpt:11300 ACCEPT udp -- 10.132.103.204 anywhere udp dpt:11300 36
  • 37.
  • 38.
  • 39.
    Beanstalkd queues +Supervisor 39
  • 40.
  • 41.
    Issue Laravel doesn't understandthat the request came from a client and not from the load balancer 41
  • 42.
    Trusted Proxy $ composerrequire fideloper/proxy # Register FideloperProxyTrustedProxyServiceProvider $ php artisan vendor:publish --provider="FideloperProxyTrustedProxyServiceProvider" // Http/Kernel.php protected $middleware = [ //... FideloperProxyTrustProxies::class, ]; // config/trustedproxy.php return [ 'proxies' => [ '192.168.10.10', // Load balancer's IP address ], //... 42
  • 43.
    Issue How to servesome files from a single server? 43
  • 44.
    Nginx Websocket Proxy upstreamwebsocket { least_conn; server 10.xx.xx.xx:2095; #websocket server ip } location /socket.io { proxy_pass http://websocket; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_connect_timeout 300; proxy_send_timeout 300; proxy_read_timeout 300; send_timeout 300; } 44
  • 45.
  • 46.
    Master/Slave Config //... 'connections' =>[ 'mysql' => [ 'write' => [ 'host' => env('DB_WRITE_HOST', env('DB_HOST', '127.0.0.1')), ], 'read' => [ 'host' => [ env('DB_WRITE_HOST', env('DB_HOST', '127.0.0.1')), env('DB_READ_HOST', env('DB_HOST', '127.0.0.1')), ], ], 'backup' => [ 'host' => env('DB_BACKUP_HOST', env('DB_READ_HOST', '127.0.0.1')), 'arguments' => '--single-transaction --skip-tz-utc', ], //... ]; 46
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
    Monitoring, Security &Op2miza2on • CDN • Monitors • Logs e excep3ons centralized • Backups • Load tests 59
  • 60.
  • 61.
  • 62.
    /** * Set theCloudFlare conneting IP and https if applied. * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, Closure $next) { if (! app()->environment('production')) { return $next($request); } if ($request->server->has('HTTP_CF_CONNECTING_IP')) { if ($request->isSecure()) { $request->server->set('HTTPS', true); } $request->server->set('REMOTE_ADDR', $request->server->get('HTTP_CF_CONNECTING_IP')); return $next($request); } logf('CF: Blocked request to %s from %s', [$request->fullUrl(), $request->ip()]); return new Response('', 404); } 62
  • 63.
  • 64.
    Issue How to clearthis cache automa0cally? 64
  • 65.
    /** * Get thepath to a versioned asset file. * * @param string $asset * @return string */ function versioned($asset) { static $production, $base, $deploy, $sha; $production = app()->environment('production'); if (is_null($base)) { $base = $production ? 'https://static.bulldesk.com.br/' : '/'; } if ($production === false) { return $base.$asset . '?v=' . str_random(3); } if (is_null($deploy)) { $deploy = public_path('build/deploy'); } if (is_null($sha) && file_exists($deploy)) { $sha = file_get_contents($deploy); } if (! empty($sha)) { return $base.$asset . '?v='.$sha; } return $base.$asset; } <script src="{{ versioned('build/scripts/app.js') }}"></script> //https://static.bulldesk.com.br/build/scripts/app.js?v=fbe06e04c4f8ddd1c94c63f3a001807bace679e0 65
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
    Issue How to centralizelogs and excep3ons? 72
  • 73.
  • 74.
  • 75.
    // LogServiceProvider.php /** * Thepapertrail log format. * * @var string */ protected $papertrailFormat = '%channel%.%level_name%: %message% %extra%'; /** * Configure the application's logging facilities. * * @return void */ public function boot() { if ($this->app->environment('production', 'staging')) { $syslog = new MonologHandlerSyslogHandler('laravel'); $formatter = new MonologFormatterLineFormatter($this->papertrailFormat); $syslog->setFormatter($formatter); $this->app['log']->getMonolog()->pushHandler($syslog); } } 75
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
    Backups $ composer requirebackup-manager/backup-manager # OR $ composer require backup-manager/laravel # OR $ composer require spatie/laravel-backup 81
  • 82.
    (...) /** * Execute thebackup command. * * @return void */ public function handle() { $database = $this->option('database') ?: 'mysql'; $destination = $this->option('destination') ?: 's3'; $destinationPath = 'dump_'. (new Carbon)->format('Y_m_d_H_i_s').'.sql'; $compression = 'gzip'; $this->info('Dumping database and uploading...'); $destinations = [new Destination($destination, $destinationPath)]; $this->backupProcedure->run($database, $destinations, $compression); $completePath = 'database/' . $destinationPath; $this->info(sprintf('Successfully dumped %s, compressed with %s and store it to %s at %s', $database, $compression, $destination, $completePath )); $last = new Carbon('last day of this month'); // Delete file in 10 days if this not the last if ((new Carbon)->isSameAs('d/m/Y', $last)) { $this->info('Scheduled job to delete the backup file ' . $completePath . ' in 10 days.'); dispatch((new DeleteFile($completePath . '.gz', 's3_backup'))->delay(24 * 60 * 60 * 10)); } } 82
  • 83.
  • 84.
  • 85.
  • 86.
    1. Don't op*mizeor escale prematurely, but be prepared for this. 86
  • 87.
    2. Time ismoney. 87
  • 88.
    3. Use microservicesonly if you can maintain them. 88
  • 89.
    4. Study/learn andenjoy everything the internet gives you for free. 89
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.