Boris Guéry

Hacking the web since 1997.

‘Proxmox 3 + Vrack 1.5 With Both Public and Private Networks on Containers Using RIPE Block’

Afin de pouvoir assurer un meilleur service avec l’augmentation de notre traffic nous avons fait l’acquisition d’un nouveau serveur.

Depuis le black-out (sold-out) chez OVH, nous avons dû patienter et prendre en main les nouvelles gammes.

Le choix a finalement était assez simple car pour notre usage et nos besoins seul la gamme Infrastructure nous convient.

En effet c’est la seule qui permet l’utilisation d’un vrack.

Cependant le vrack a évolué et il est maintenant en version 1.5, par ailleurs il est “gratuit”, du moins inclut dans le prix du serveur, et se commande à la livraison du serveur dans le manager v6.

Une fois la machine reçu, j’installe la distribution Proxmox fournit par OVH, je passerai sur le repartionnement des disques car par défault, le partionnement ne laisse pas assez d’espace libre au volume logique pour réaliser des snapshots pour les backups.

La configuration de l’host node

Une fois l’installation terminait, il faut tout d’abord activer la seconde interface eth1 du serveur, c’est cette interface qui est reliée au vrack:

ifconfig eth1 up devrait faire l’affaire, on peut vérifier l’activation de l’interface avec la commande ethtool eth1 | grep "Link detected" (il se peut qu’il faille attendre quelques secondes avant que l’activation soit effective).

Il faut ensuite configurer un nouveau bridge, dans le fichier /etc/network/interfaces on y ajoute donc un nouveau bridge utilisant notre eth1 :

1
2
3
4
5
6
7
8
9
10
# bridge vrack 1.5
auto vmbr2                    # Je l'ai nommé vmbr2 car dans ma configuration il existe déjà vmbr0 & vmbr1
iface vmbr2 inet static
    address   172.16.0.128    # Ici j'en profite pour assigner une addresse sur le réseau 172.16.0.0/12 qui me permettra d'avoir mon hôte disponible sur le vlan
    netmask   255.240.0.0     # C'est le masque qui correspond au réseau 172.16.0.0/12
    broadcast 172.31.255.255  # Le broadcast est toujours la dernière ip du réseau
    gateway   172.31.255.254  # Et la gateway l'avant dernière
    bridge_ports eth1         # Le bridge s'effectue sur l'interface eth1
    bridge_stp   off
    bridge_fd    0

Il suffit ensuite de ré-initialiser les interfaces avec la commande service networking restart (il faut parfois la lancer deux fois).

On peut vérifier avec ifconfig, nous devrions entre autre avoir ceci :

1
2
3
4
5
6
7
vmbr2     Link encap:Ethernet  HWaddr 00:25:90:a7:43:49
    inet addr:172.16.0.128  Bcast:172.31.255.255  Mask:255.240.0.0
    UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
    RX packets:159498973 errors:0 dropped:0 overruns:0 frame:0
    TX packets:184541095 errors:0 dropped:0 overruns:0 carrier:0
    collisions:0 txqueuelen:0
    RX bytes:99636994421 (92.7 GiB)  TX bytes:52538175446 (48.9 GiB)

A partir de maintenant, nous devrions pouvoir pinger la gateway 172.31.255.254 ainsi que toutes les machines déjà présente (s’il y en a) sur le vlan.

La configuration du guest container

Pour ajouter un container au vlan c’est assez simple, il suffit à présent d’ajouter dans l’interface de Proxmox une ip du réseau 172.16.0.0/12 en venet, et ça fonctionne.

En revanche dès que l’on veut faire cohabiter une ip privée et une ip publique, il faut un peu tweaker le routage.

Avec une ip failover routée sur le serveur hôte

Il faut configurer l’ip publique AVANT l’ip privée, les deux en venet dans l’interface de Proxmox. Ensuite il faut configurer correctement la table de routage, par défault la table de routage doit ressembler à ceci :

1
2
3
4
root@machine128:/# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         0.0.0.0         0.0.0.0         U     0      0        0 venet0

Il faut ajouter une route pour s’assurer du routage correcte pour le vlan :

route add -net 172.16.0.0 gw 172.16.0.128 netmask 255.240.0.0

Il faut utiliser en gateway l’ip privée de la machine.

À partir de maintenant nous devrions pouvoir pinger l’ensemble du vlan ainsi qu’accèder à “l’internet”.

Avec une ip provenant d’un bloc IP RIPE routé sur le vrack 1.5

Dans le cas d’un bloc ip ripe routé sur le vrack la configuration est un peu différente.

La configuration pour l’ip privée reste la même, cependant, il ne faut pas utiliser le venet pour l’ip publique.

Dans l’interface de Proxmox il faut créer une nouvelle interface veth en utilisant vmbr2 (dans notre cas), on nommera l’interface eth0. Pour le reste il n’y a pas de configuration particulière, on peut laisser la configuration automatique.

On démarre la machine virtuelle pour configurer la nouvelle interface.

Supposons que vous ayez fait l’acquisition du bloc 1.2.3.4/28.

Dans /etc/network/interfaces (sur debian/ubuntu), nous devons ajouter ceci:

1
2
3
4
5
6
7
auto eth0
iface eth0 inet static
    address   1.2.3.5          # Nous ne devons jamais utilisé la première ip d'un bloc
    netmask   255.255.255.240  # Le netmask correspondant à notre bloc
    gateway   1.2.3.18         # L'avant dernière ip de notre bloc
    network   1.2.3.4
    broadcast 1.2.3.19         # La dernière ip du bloc

Il faut à présent activer les interfaces : /etc/init.d/networking restart:w

Puis reconfigurer la table de routage, pour cela il faut supprimer toutes les routes existantes puis créer deux nouvelles routes :

route del -net default gw 0.0.0.0, on supprime la route par défault sur le venet0 route del -net 1.2.3.4 gw 0.0.0.0 netmask 255.255.255.240 dev eth0 À cette instant il doit rester une seule route, celle par défault qui doit ressembler à ceci :

1
2
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         1.2.3.18        0.0.0.0         UG    100    0        0 eth0

Et on rajoute la route pour notre VLAN :

route add -net 172.16.0.0 gw 172.16.0.128 netmask 255.240.0.0 dev venet0

Pour finir on ping la gateway de notre vrack pour s’assurer que tout fonctionne :

ping 172.31.255.254

Are SSD Really Worth?

I’ve recently faced a situation where I had to ask myself, are ssd worth the price?

We are willing to upgrade our infrastructure and I took a look at the new dedicated servers offered by our hosting provider, OVH.

We used to have only SSD for our servers, but to reduce our hosting cost, I realized that SSD disks where ± 40 € more expensive.

So are they worth?

I had the basic assumption, which is still right anyway, that SSD are quicker, read and write access are really fast. I remember when I bought for myself, it was heaven.

But, in a distributed architecture where one service is ran by one server (or at least a virtual server in our proxmox cluster), are the disk access a real problem?

We are running several frontend, powered by Apache, on which our PHP application is processed. We use Symfony 2, and a lot of dependencies make the memory fingerprint high, as well as the number of file to load.

But, here is the key, we use an opcode cache, so most of the file executed by PHP is read from memory, which no disks access, at all (once the cache is warmed up, obviously).

Enough assumptions, let’s monitor and benchmark.

To have some numbers to play with, I used sar and iostat which is part of the sysstat suit. I also used iotop which is like top but sort by disk read/write.

To get sample of the read/write bandwidth I used iotop in batch mode:

iotop -o -P -b -k -qqq | tr -d \[\]\- | tee -a iotop.log

-o means display only the process which either reads or writes, -P groups all thread on under the same process, -b starts the batch, non-interactive mode, -qqq means quiet, quiet, quiet (do not return headers or summary).

tr is used to clean a bit the output, and tee to both display and write to a file.

After a given time, we can start to analyse data. I used awk for this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
awk '
BEGIN {
    printf "\nprocess\t\tread\t\twrite\n"
}
{
    process=$12;
    kb_read=$4;
    kb_write=$6;
    read[process]  += kb_read;
    write[process] += kb_write;
    l = length(process);
    m = m < l ? l : m;
}
END {
    for (process in read) {
        printf "%s%*s\t%.2f\t\t%.2f\n", process, m - length(process), "", read[process], write[process];
    }
}' iotop.log | sort -V -r -k 3

Sample result:

process read write
apache2 9954.10 36519.83
kjournald 7.79 27237.70
tee 3.90 2047.48
rrdcached 23.40 2016.19
rsyslogd 0.00 1977.26
puppet 0.00 70.19
vi 0.00 11.69
smtp 0.00 7.79
cleanup 0.00 7.79
qmgr 0.00 0.00
named 19.51 0.00
bash 19.51 0.00

(in kb/s)

This has been sampled for around 3 hours, so given the apache results it’s an average of 12 mb/h, 170 kb/m and 2.83 kb/s.

I also used inotifywatch to determine the number of read/write access and compute the average number of access to the filesystem.

That’s not much, in comparison, our mysql databases results

Mobile and Tablet Device Detection With Varnish

I recently had to work on our mobile website, using a RESS strategy. All worked great until I had to determine if the connecting device was a mobile, a tablet, or a generic desktop web browser.

It could have been simple by using some server-side framwework like MobileDetect. While it looks complete and seems to precisely detect a lot of device it requires to be processed on each requests (or at least at the first request).

And this is a problem. Most website have one entry point, the homepage, it is often (it is in our case), the most viewed page.

We use Varnish a lot, which provides us really good performance for no costs, and we are really happy with it. But adding a device detection to our application on the server-side would have broke our caching mechanism, and it is totally unacceptable.

As far as I know, it exists only two solutions to do device detection on the Varnish side, a WURFL VMod provided by ScientiaMobile, and the DeviceAtlas Vmod. They are both commercial solutions, and expensive ones, while they are probably worth the price, it’s still too much for us.

Basically my needs were the following: I needed to detect both the device type and the device os, at least for the four major OS on the market, iOS, Android, Blackberry, and Windows 8/Windows Phone.

I don’t need to know about device capabilities, I just need to know if it is a mobile, a tablet, or a desktop browser, and know the OS to offer our users the proper and targeted native mobile application while keeping the benefit of HTTP caching with Varnish.

So I end up writing my own. For the sake of simplicity and to save me time, I decided to go with VCL instead of diving into the abyss of the Varnish API. VCL is not C, yet it is powerful enough for this task.

The freshly written subroutine, check a bunch of common pattern in the User-Agent to detect the device and sets two pseudo variables (as part of the request object) Device-Type which can be either of mobile, tablet or generic (desktop or unkwown), and the Device-OS (known OS are iOS, Android, Blackberry, Windows 8 and Windows Phone).

The subroutine is tested against 200 User-Agent I collected from our access log and works pretty well.

Still, I plan to handle a switch option directly in Varnish by hooking a specific URL and sets a cookie to avoid redirect when the user decides to.

The project Varnish Device Detect is Github, feel free to improve or add tests.

PHP Deployment With Capistrano 3.x

Since one year or two (I pushed a quick tutorial some months ago on Github), I intensively use Capistrano as well as its “Symfony” variant, capifony. And I’m really happy. I deploy in production ten times a day, and it really helps getting things done, securely.

For a new project I work on, I decided to go with it, again. But it’s time to evolve, so I decided to go with Capistrano 3.x. As of this day (2013-11-27) the version 3.0 seems stable, yet not complete though.

The documentation is still at its early days, and I’m obviously not a Rubyist. However, things have clearly evolved, in the good way, and now Capistrano is not shipped with all the Ruby/Rails tasks that we, PHP guys, don’t need. The tool is not bloated with all those things and that’s good for us.

Anyway, I decided to dive in the process of deploying a project with the new Capistrano 3.

As the migration note states, it is recommended to start over, do no try to make Capistrano 2.x Capfile works with Capistrano 3.x, it won’t, work, at, all.

One of the first thing I mentioned when installed Capistrano 3.0 is that I was not able to override paths! By default, Capistrano 3.x cap install (command which generates a skeleton deployment scripts) generates files in config/ folder. Maybe that’s the way to go in Ruby projects, but that’s not my taste, and I sometimes like to make things work how I want they work (yeah I’m that kind of guys).

Override the capistrano/setup.rb was not enough because the stages management, which by the way, are now bundled in Capistrano 3, is hard coded in the module.

Luckily, an issue had been posted a while ago, and luckily again, it got merged.

Being merged on an unstable branch, I had to build the gem myself from source, here we go:

1
2
3
$ git clone https://github.com/capistrano/capistrano.git
$ gem build capistrano.gemspec
$ gem install capistrano-3.1.0.gem

Easy isn’t it?

Now that we’ve a more customizable version of Capistrano, we can get back to work.

So the first thing to do is to generate the default deployment scripts shipped by Capistrano:

1
$ cap install

I like my deployment scripts to live in, guess, the deployement/ folder, I move them there.

1
2
$ mv config deployment
$ mv deployment/deploy deployment/stages

It makes more sense to me to move stages configuration there.

So far we have a Capfile which looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
# Override the path before running the setup
set :deploy_config_path, 'deployment/deploy.rb'
set :stage_config_path, 'deployment/stages/'

# Load DSL and Setup Up Stages
require 'capistrano/setup'

# Includes default deployment tasks
require 'capistrano/deploy'

# Override the default path to bundle deployments scripts and tasks
Dir.glob('deployment/tasks/*.cap').each { |r| import r }`

And our folder structure looks like this:

1
2
3
4
5
6
deployment
├── deploy.rb
├── stages
│   ├── production.rb
│   └── staging.rb
└── tasks

One deploy.rb global configuration, two stages (production & staging), no custom tasks, yet.

To get a minimal working deployment script, we have to configure some variables:

In deployment/deploy.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Give our application a name
set :application, 'app'

# The repository path, must be accessible from the remote server
set :repo_url, 'ssh://domain.tld/repositories/project.git'

# The path where to deploy things
set :deploy_to, '/var/www/domain.tld'

# The default tasks shipped by the install task
namespace :deploy do
  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      # Your restart mechanism here, for example:
      # execute :touch, release_path.join('tmp/restart.txt')
    end
  end
  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end
    end
  end
  after :finishing, 'deploy:cleanup'
end

Note that the deploy_path variable is set to /var/www/domain.tld but the actually deployed project will live in releases/[some-release-date] and symlinked to current/, so any virtualhost should have its document root sets to current/public folder.

To authenticate against the remote repository, I use an ssh-agent with a forwarding capabilities. It avoids to create unnecessary ssh keys on each servers.

Now, we need to configure the production stage (or the staging stage, no matter):

1
2
set :stage, :production
server 'production.domain.tld', user: 'borisguery', roles: %w{web app}

Dead simple.

Time to test, run cap production deploy. Capistrano should now be able to connect to the remote server, create the folders (your deployment user should have the permissions, use POSIX ACL for this), and clone the remote repository.

We are done for now, the next step is to create custom tasks. For now, I’m only interested in two things:

Composer has already a gem for this, so we’ll use it.

For the sake of simplicity I’ll start to add it to a Gemfile, it is very similar to our composer.json, it is used to define dependencies requirements for a project.

1
2
3
source 'https://rubygems.org'

gem 'capistrano-composer'

Then make sure the gem is actually loaded by Capistrano, in Capfile:

1
require 'capistrano/composer'

It will automagically execute itself after the deploy:updated tasks.

Now, let’s take a look at grunt. Grunt relies on npm/bower packages to execute tasks. In order to properly run grunt on your remote server, we need to make sure nodejs & npm are available.

Grunt itself being a library, not an actually cli command, we also need to install the grunt-cli npm package, it should be done once, and globally on the remote server. (In my case it is part of my provisioning process using puppet)

1
$ sudo npm -g install grunt-cli

And guess what, the same apply to bower:

1
$ sudo npm -g install bower

Our server is ready to get fed with some bytes of deployment process.

Now, let’s add the according gems to our Gemfile

1
2
3
require 'capistrano/npm'
require 'capistrano/bower'
require 'capistrano/grunt'

I decided to use a Capistrano task to resolve bower dependencies but it could have been done within grunt itself with the proper plug-in.

Configure the task we want grunt to run:

1
set :grunt_tasks, 'dist'

If not set, it will execute the default task, as if we ran grunt command alone.

To make sure our tasks will execute in the correct order (hence, npm:install, bower:install before grunt), we add the following statement at the end of deploy.rb:

1
after 'bower:install', 'grunt'

We are done.

Restaurer Un Fichier Supprimé Avec Git

Oops, j’ai supprimé un fichier dans un précédent commit par erreur…

Pour le récupérer, avec git, c’est simple :

1
2
3
git rev-list -n 1 HEAD -- <nom_du_fichier>
git checkout <le_commit_retourne_par_la_commande_precedante>^ -- <nom_du_fichier>
git commit <nom_du_fichier>

Keywords Checker Git Pre-commit Hook

Dans la même veine que le précédent article sur mon pre-commit hook qui valide la syntaxe des fichiers PHP allant être comités, j’ai développé un petit hook qui vérifie la présence de certains mot clés dans le code.

Ce n’est bien sûr pas toujours crucial, mais d’une manière générale ce type de fonctions ne devrait jamais être intégrer au dépôt, car s’il peut s’avérer difficile de débugguer un bout code contenant un die() (qu’on a bien sûr oublié depuis longtemps…), cela peut également poser des problèmes de sécurité (var_dump($user->getPassword());), et sans oublier les erreurs liées à console.log(element) qui peuvent faire échouer tout un script js si Firebug n’est pas installé sur la machine cliente.

Bien entendu, vous ne devriez pas faire appel à ce genre de fonction pour débugguer votre code, car vous effectuez déjà du pas à pas avec XDebug, même sur Mac OS X ;)

Et ça se trouve, encore, sur github (zip).

Par défaut, la configuration vérifie les fichiers portant l’une des extensions suivantes :

  • .php
  • .phtml
  • .js

Et à la recherche des mot clés/fonctions suivantes :

  • var_dump()
  • die()
  • console.(log|debug|warn|info)

Pour ajouter des extensions ou des mot clés il faut respectivement modifier les variables EXTENSIONS_REGEX et KEYWORDS_REGEX (ce sont des expressions rationnelles étendues (ERE)).

PHP Lint Git Pre-commit Hook

Défenseur d’un code de qualité, je suis friand d’outils qui me permettent à la fois d’améliorer celui-ci.

Pour ceux qui ne connaissent pas encore le principe des “lint” il s’agit de programmes qui effectue une analyse statique du code. Entre autre chose, ces outils analyse et valide la syntaxe de votre code mais également apporte quelques informations avertissement d’un éventuel problème, tel que des variables non définie par exemple.

PHP fournit nativement un tel outil, pour l’utiliser, il suffit d’ajouter l’option -l

1
2
3
4
5
bguery@joyless:project (master)$ php -l test.php
PHP Parse error:  syntax error, unexpected T_STRING in test.php on line 4

Parse error: syntax error, unexpected T_STRING in test.php on line 4
Errors parsing test.php

Pour cette raison, j’ai forké une première version d’un hook que j’avais tenté d’utiliser.

Voici ce que ça donne :

exemple workflow

Disponible sur github (zip).

Phpsh - Console Interactive PHP

Quand j’utilise Python, c’est simple :

1
bguery@joyless:bin $ python

Et j’obtiens :

1
2
3
4
5
6
7
8
9
10
11
12
Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> foo = "Hello World"
>>> prin foo
  File "<stdin>", line 1
    prin foo
           ^
SyntaxError: invalid syntax
>>> print foo
Hello World
>>>

Avec php, je dois faire ça :

1
bguery@joyless:bin $ php -a

Et j’obtiens ça :

1
2
3
4
5
6
7
Interactive shell

php > $foo = "Hello World";
php > var-dump($foo);
PHP Parse error:  syntax error, unexpected T_VAR in php shell code on line 1
php > vardump($foo);
PHP Fatal error:  Call to undefined function vardump() in php shell code on line 1

Certes, je suis vraiment idiot, mais il faut le reconnaitre, ce n’est pas évident de tester de petit bout de code en PHP, il existe bien codepad, mais c’est pas le plus pratique.

La console interactive de PHP, n’est pas, réellement, interactive.

Et récemment j’ai découvert phpsh, un petit utilitaire qui remplace le shell interactive de PHP écrit en partie en Python par Facebook, en apportant quelques options bien pratiques.

Exemple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
php> $foo = "Hello World"
php> vardump($foo)
Not executing input: Possible call to undefined function vardump()
php> d array_merge

# array_merge

(PHP 4, PHP 5)

array_merge -- Merge one or more arrays

### Description

array array_merge ( array $array1 [, array $array2 [, array $... ]] )

Merges the elements of one or more arrays together so that the values of one are appended to the end of the previous one. It returns the resulting array.

If the input arrays have the same string keys, then the later value for that key will overwrite the previous one. If, however, the arrays contain numeric keys, the later value will not overwrite the original value, but will be appended.

If only one array is given and the array is numerically indexed, the keys get reindexed in a continuous way.

### Parameters

array1

Initial array to merge.

array

Variable list of arrays to recursively merge.

### Return Values

Returns the resulting array.

###

php> = 2 +
 ... 2
4
php> = array(array(1,2,3), array("a" => "b", "c" => "d"), "e", "g")
array(
  0 => array(
    0 => 1,
    1 => 2,
    2 => 3,
  ),
  1 => array(
    "a" => "b",
    "c" => "d",
  ),
  2 => "e",
  3 => "g",
)
php> q

Un regret tout de même, pas d’auto-complétion.

Les sources sur github.

Coloration Syntaxique Des INI Avec Syntax Highlighter

J’utilise Syntax Highligther pour colorer mes codes sources, il existe un grand nombre de brosses disponibles, mais aucune pour les fichiers ini, pourtant utilisés, surtout par les développeurs PHP et encore plus par ceux qui utilisent Zend Framework.

Pour documenter un peu ma librairie d’extensions pour Zend Framework, je voulais pouvoir clairement afficher la source de mes fichiers ini de configuration (application.ini par exemple).

L’API de Syntax Highlighter permet de créer ses propres “brushes”, j’ai donc créé la mienne en m’inspirant des autres brushes.

Example:

Le code source shBrushIni.js (github)

Débugger en Ligne De Commande Avec XDebug

Xdebug, c’est bien, surtout lorsqu’il s’agit de débugger des applications web complexes. Mais parfois, on utilise PHP pour d’autre tâches, comme des petits scripts à lancer en ligne de commande, ou encore une application qui permet de lancer certaines tâches à partir de la CLI.

En fait, il suffit simplement de rajouter une variable d’environnement avant de lancer le script, pour me faciliter le travail, j’ai fait un petit raccourci bash :

1
2
3
4
5
6
7
bguery@joyless:~ $ vi ~/bin/xdebug

#!/bin/bash
XDEBUG_CONFIG="idekey=zs8-xdebug" php $@

bguery@joyless:~ $ chmod +x ~/bin/xdebug
bguery@joyless:~ $ xdebug script.php

N’oubliez pas d’avoir le fichier ouvert dans votre IDE (en tous cas pour Eclipse / Zend Studio), Eclipse vous demandera ensuite si vous acceptez la session de debugage.

Note: On pourra également initialisé un débugage à distance à l’aide du paramètre de configuration : xdebug.remote_host à utiliser conjointement avec l’option -d de PHP.

Example:

1
bguery@joyless:$ xdebug -dxdebug.remote_host=192.168.0.1 script.php