obsolete

OpenShift et DIY

Nous allons ici déployer et exécuter dans le OpenShift une application WebSockets 7 autoportée. Donc, sans serveur Apache. Pour le fun… Nous allons dans un premier temps uniquement faire en sorte de déployer un serveur WebSockets, sans trop nous soucier d’un cas d’utilisation en particulier. Une application qui diffuse aux clients connectés une série de messages fera parfaitement l’affaire. Nous aborderons ultérieurement un cas d’utilisation un peu plus concret. L’ambition de cet article est de faire apparaître un certain nombre de contraintes imposées par les stratégies de sécurité de OpenShift. Ceci explique la structure très expérimentale et très incrémentale de ce qui va suivre. Un serveur WebSockets demande une infrastructure applicative dédiée, que n’offre aucune des cartouches disponibles. Nous allons donc devoir créer cet environnement. Pour cela, nous allons nous orienter vers les cartouches “Do It Yourself”.

Les WebSockets permettent au client d’ouvrir une connexion persistante au serveur. De cette façon, le serveur peut transmettre des informations au client, et le client n’a plus à sonder le serveur à intervalles réguliers dans l’attente de modifications.

RHC

Pour commencer, il s’avère parfois laborieux d’installer localement la suite RHC, la commande permettant d’administrer OpenShift. Il est beaucoup plus simple de passer par un container clés-en-mains. Pour bien commencer, démarrez donc ce container Docker :

sudo docker run --rm --name rhc -ti -v ~/.openshift:/root/.openshift -v ~/.ssh:/private bigm/rhc bash

Dans ce container, effectuez l’installation de l’environnement :

rhc setup

Pour envisager sereinement la suite, il peut être utile de savoir lister les images Docker qui tournent sur votre machine. Si vous faites de nombreux tests avec le container précédent, des redémarrages de l’image peuvent être nécessaires, et ce, afin de pouvoir reprendre sur une base propre et saine :

renald@venantvr:~$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
8f25177bfbbc        bigm/rhc            "/run.sh bash"      24 minutes ago      Up 24 minutes                           rhc

Pour arrêter le container en question, tapez dans la console :

renald@venantvr:~$ sudo docker kill 8f25177bfbbc

Passons à la suite : l’installation de PHP7.

Installation de la cartouche PHP7

Ensuite, nous allons installer PHP7 dans votre noeud DIY, grâce à la commande suivante (sans se connecter à l’application, faites ceci à l’intérieur de l’image Docker précédemment lancée…) :

rhc cartridge add -a venantvr --env OPENSHIFT_PHP_VERSION=7.0.7 http://cartreflect-claytondev.rhcloud.com/github/boekkooi/openshift-cartridge-php

Il s’agit d’une cartouche non officielle. Les versions de PHP officiellement supportées par OpenShift sont les versions 5.3 et 5.4. Pour l’instant, remarquez que nous ne nous sommes pas encore connectés via SSH à la machine distante. Placez-vous maintenant dans le répertoire /root/.openshift du container. Ce volume est lié au répertoire ~/.openshift local. Ce répertoire devient notre répertoire de travail. Ouvrez maintenant une session SSH vers l’application OpenShift :

[venantvr-corbakatak.rhcloud.com 5994a20789f5cf1fb100000c]\> php -v
PHP 7.0.7 (cli) (built: Jun  7 2016 06:55:11) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies

PHP7 est bien installé.

Synchronisation

Dans le container, placez-vous dans le répertoire app-root/repo/. C’est dans ce répertoire que se trouvent les sources déployées lors de chaque commande “git push” que vous invoquerez localement. Lorsque vous invoquez cette commande, l’application est instantanément déployée sur le noeud applicatif. Déconnectez-vous du serveur et revenez dans l’image Docker en cours d’exécution. Ramenez le code source en local, pour une première synchronisation.

git clone ssh://5994a20789f5cf1fb100000c@venantvr-corbakatak.rhcloud.com/~/git/venantvr.git/

La documentation des cartouches DIY est sommaire. Par défaut, le template DIY embarque un script Ruby qui écoute sur un port donné de la machine distante, le port 8080. Cependant, l’application Ruby sort de l’infrastructure par le port 80. Le script en question affiche la page de bienvenue OpenShift :

L’application par défaut

La bonne nouvelle est que nous disposons donc à minima d’un serveur HTTP permettant de délivrer des fichiers. Le service écoute le port 8080 du container OpenShift, nous pouvons le vérifier en ouvrant le script Ruby :

#!/usr/bin/env ruby
require 'webrick'
include WEBrick

config = {}
config.update(:Port => 8080)
config.update(:BindAddress => ARGV[0])
config.update(:DocumentRoot => ARGV[1])
server = HTTPServer.new(config)
['INT', 'TERM'].each {|signal|
  trap(signal) {server.shutdown}
}

server.start

L’utilisation de ce port est l’une des conditions imposées pour écrire et déployer des applications personnelles sur OpenShift. Ce binding est aussi vérifiable par les variables d’environnement. En SSH dans le container, et pour vous en assurer, faites :

[venantvr-corbakatak.rhcloud.com 5994a20789f5cf1fb100000c]\> echo $OPENSHIFT_DIY_IP
[venantvr-corbakatak.rhcloud.com 5994a20789f5cf1fb100000c]\> echo $OPENSHIFT_DIY_PORT

Ces commandes vous donneront l’adresse IP et le port sur lesquelles écoute (et doit écouter) le serveur HTTP. L’application personnalisée est démarrée et stoppée à partir de 2 scripts. Ces scripts de démarrage et de fermeture sont respectivement .openshift/action_hooks/start et .openshift/action_hooks/stop. Dans le répertoire de travail, naviguez dans le répertoire .openshift/action_hooks :

Différentes cartouches peuvent supporter différents “hooks”, selon leur cycle de vie.

renald@venantvr:~/.openshift/venantvr/.openshift/action_hooks$ cat start
#!/bin/bash
# The logic to start up your application should be put in this
# script. The application will work only if it binds to
# $OPENSHIFT_DIY_IP:8080
nohup $OPENSHIFT_REPO_DIR/diy/testrubyserver.rb $OPENSHIFT_DIY_IP $OPENSHIFT_REPO_DIR/diy |& /usr/bin/logshifter -tag diy &
renald@venantvr:~/.openshift/venantvr/.openshift/action_hooks$ cat stop
#!/bin/bash
source $OPENSHIFT_CARTRIDGE_SDK_BASH

# The logic to stop your application should be put in this script.
if [ -z "$(ps -ef | grep testrubyserver.rb | grep -v grep)" ]
then
    client_result "Application is already stopped"
else
    kill `ps -ef | grep testrubyserver.rb | grep -v grep | awk '{ print $2 }'` > /dev/null 2>&1
fi

Ces scripts sont très importants, puisqu’ils nous expliquent précisément comment lancer (et stopper) une application, et les contraintes de ports à respecter. Ainsi, l’adresse IP définissant l’interface sur laquelle écouter est passée en paramètre.

Notre application

Pour l’implémentation du serveur WebSockets, nous allons opter pour la librairie Ratchet et suivre le tutoriel suivant. Nous allons passer l’adresse IP d’écoute en paramètre au fichier socket.php, comme suit :

require '../vendor/autoload.php';  
use Ratchet\MessageComponentInterface;  
use Ratchet\ConnectionInterface;

require 'chat.php';

if (isset($argv) == true)
{
    $host = $argv[1];
}

if (isset($host) == false)
{
    $host = '127.0.0.1';
}

echo 'Listening on : ' . $host . "\r\n";

// Run the server application through the WebSocket protocol on port 8080
$app = new Ratchet\App('venantvr-corbakatak.rhcloud.com', 8080, $host, $loop);
$app->route('/chat', new Chat, array('*'));

$app->run();

Dans le code précédent, nous instancions une application Ratchet. Cette application embarque un serveur WebSockets, et elle expose une logique de chat, derrière une route. Pour être le plus performant possible, cette application Ratchet implémente un système de requêtes non bloquantes. Le constructeur de l’application créée ci-dessus prend en paramètre le nom de la machine devant héberger le service, le port d’écoute, l’adresse IP définissant l’interface réseau, et un objet responsable de la boucle d’événements. De plus amples informations sur cette fameuse boucle sont disponibles ici. Nous allons aussi adapter les fichiers .openshift/start et .openshift/stop de manière à leur faire exécuter et stopper la commande suivante, en prenant soin de lui passer les paramètres adéquats :

php websockets/socket.php 127.0.0.1

Le fichier ./openshift/start devient :

#!/bin/bash
# The logic to start up your application should be put in this
# script. The application will work only if it binds to
# $OPENSHIFT_DIY_IP:8080
nohup php $OPENSHIFT_REPO_DIR/websockets/socket.php $OPENSHIFT_DIY_IP |& /usr/bin/logshifter -tag websockets &

Et le fichier .openshift/stop :

#!/bin/bash
source $OPENSHIFT_CARTRIDGE_SDK_BASH

# The logic to stop your application should be put in this script.
if [ -z "$(ps -ef | grep socket.php | grep -v grep)" ]
then
    client_result "Application is already stopped"
else
    kill `ps -ef | grep socket.php | grep -v grep | awk '{ print $2 }'` > /dev/null 2>&1
fi

Une fois le code mis à jour, la commande “git push” lancée depuis l’image Docker permet de déployer l’application. Pour tester tout ceci, installez dans votre navigateur une extension WebSockets (celle-ci fait parfaitement l’affaire sur Firefox ou Chrome). Si l’application est lancée localement sur le port 8080, elle n’est plus à présent accessible depuis l’extérieur en passant par le port 80, mais par le port 8000. Le firewall OpenShift route son trafic vers (pour ce qui nous intéresse…) 2 reverse-proxies : un proxy HTTP/S et un proxy mixte HTTP/WS/S. Plus précisément, le port 80 adresse le protocole HTTP. Le port 443 adresse le protocole HTTPS. Le port 8000 adresse directement le protocole WS, ou HTTP, tandis que le port 8443 adresse le protocole WSS, ou HTTPS.

En effet, toutes les applications domiciliées sur le domaine “*.rhcloud.com” bénéficient gratuitement d’un certificat de chiffrement TLS. HTTPS et WSS sont donc naturellement exploitables par nos applications, sans qu’aucun travail supplémentaire ne s’impose. Le protocole et le port suffisent à adresser l’application correspondante.

A présent, le serveur WebSockets semble accessible de l’extérieur puisque la connexion est établie.

Que disent les logs ?

Si la connexion semble établie, la seule façon de le vérifier consiste dans un premier temps à regarder les logs de l’application. Ceux-ci se trouvent dans le répertoire app-root/logs/ de la machine OpenShift. Ces logs sont aussi accessibles depuis la commande suivante :

rhc tail -a venantvr
root@fd6a4b9b9013:~/.openshift/venantvr# rhc tail -a venantvr
==> app-root/logs/php-fpm-error.log <== [20-Aug-2017 11:44:48] NOTICE: [pool www] 'user' directive is ignored when FPM is not running as root [20-Aug-2017 11:44:48] NOTICE: [pool www] 'group' directive is ignored when FPM is not running as root [20-Aug-2017 11:44:48] NOTICE: fpm is running, pid 155027 [20-Aug-2017 11:44:48] NOTICE: ready to handle connections ==> app-root/logs/websockets.log <== Listening on : xxx.xxx.xxx.xxx ==> app-root/logs/php-error.log <== #0 /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/vendor/cboden/ratchet/src/Ratchet/App.php(93): React\Socket\Server->listen(8843)
#1 /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/socket.php(32): Ratchet\App->__construct('127.10.191.1', 8080, '127.10.191.1', Object(React\EventLoop\StreamSelectLoop))
#2 {main}
  thrown in /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/vendor/react/socket/src/Server.php on line 90
[20-Aug-2017 15:44:48 UTC] PHP Fatal error:  Uncaught React\Socket\ConnectionException: Could not bind to tcp://127.0.0.1:8843: Permission denied in /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/vendor/react/socket/src/Server.php:90
Stack trace:
#0 /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/vendor/cboden/ratchet/src/Ratchet/App.php(93): React\Socket\Server->listen(8843)
#1 /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/socket.php(32): Ratchet\App->__construct('127.10.191.1', 8080, '127.10.191.1', Object(React\EventLoop\StreamSelectLoop))
#2 {main}
  thrown in /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/vendor/react/socket/src/Server.php on line 90

En fait, 3 types de fichiers sont suivis par la commande précédente :

  • app-root/logs/php-fpm-error.log : le fichier de traces de FastCGI Process Manager. Inutile dans notre cas…
  • app-root/logs/websockets.log : le fichier de traces du processus lancé à partir du fichier .openshift/start…
  • app-root/logs/php-error.log : le fichier de traces du moteur PHP à proprement parler…

Le fichier websockets.log nous indique que le serveur est à l’écoute. Par contre, dans le fichier php-error.log, nous pouvons deviner que tout ne se passe pas comme prévu, malgré l’apparente connexion au serveur WebSockets.

==> app-root/logs/php-error.log <== #0 /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/vendor/cboden/ratchet/src/Ratchet/App.php(93): React\Socket\Server->listen(8843)
#1 /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/socket.php(32): Ratchet\App->__construct('127.10.191.1', 8080, '127.10.191.1', Object(React\EventLoop\StreamSelectLoop))
#2 {main}
  thrown in /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/vendor/react/socket/src/Server.php on line 90
[20-Aug-2017 15:44:48 UTC] PHP Fatal error:  Uncaught React\Socket\ConnectionException: Could not bind to tcp://127.0.0.1:8843: Permission denied in /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/vendor/react/socket/src/Server.php:90
Stack trace:
#0 /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/vendor/cboden/ratchet/src/Ratchet/App.php(93): React\Socket\Server->listen(8843)
#1 /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/socket.php(32): Ratchet\App->__construct('127.10.191.1', 8080, '127.10.191.1', Object(React\EventLoop\StreamSelectLoop))
#2 {main}
  thrown in /var/lib/openshift/5994a20789f5cf1fb100000c/app-root/runtime/repo/vendor/react/socket/src/Server.php on line 90

Nous pouvons notamment constater une tentative d’écoute sur le port 8843, au niveau de la librairie ReactPHP, l’une des dépendances de Ratchet. ReactPHP est une librairie qui permet de traiter et d’orchestrer un certain nombre de requêtes de manière asynchrone et non bloquante. En effet, une application Ratchet est conçue pour s’interfacer et réagir à toutes sortes de sollicitations, en temps réel, de manière asynchrone. Dans cette optique, elle embarque une boucle d’événements. Par défaut, si celle-ci n’est pas définie explicitement lors de l’instanciation du serveur WebSockets, la boucle en question est instanciée depuis ReactPHP. Hors, OpenShift ne nous permet pas d’écouter le port 8843, nous devons donc contourner l’instanciation de cette boucle d’événements, en fournissant à Ratchet de manière explicite un procédé plus basique, peut-être moins performant, qui présentera le grand avantage de fonctionner face aux exigences de sécurité d’OpenShift. Nous avions écrit :

$loop = React\EventLoop\Factory::create();

/**
 * @param string        $httpHost HTTP hostname clients intend to connect to. MUST match JS `new WebSocket('ws://$httpHost');`
 * @param int           $port     Port to listen on. If 80, assuming production, Flash on 843 otherwise expecting Flash to be proxied through 8843
 * @param string        $address  IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine.
 * @param LoopInterface $loop     Specific React\EventLoop to bind the application to. null will create one for you.
 */

// Run the server application through the WebSocket protocol on port 8080
$app = new Ratchet\App('venantvr-corbakatak.rhcloud.com', 8080, $host, $loop);
$app->route('/chat', new Chat, array('*'));
$app->run();

Pour simplifier, nous écrivons :

$chat = new Chat();
$server = IoServer::factory(new HttpServer(new WsServer($chat)), 8080, $host);
$server->run();

Nous sacrifions une application Ratchet au profit d’un serveur HTTP et d’un serveur WS. Nous reviendrons ultérieurement sur la question des routes… pour le moment, nous allons simplement recevoir des messages et émettre ces messages vers les clients connectés au serveur. Dans la seconde partie de cet article, nous pouvons d’ores et déjà prévoir de délivrer, en plus des messages, la page HTML et le JS permettant aux clients de se connecter au serveur. Nous disposons d’un serveur HTTP après tout. Nous allons aussi prévoir d’héberger nos ressources derrière des routes. Actuellement, cette petite volte-face concernant l’implémentation de Ratchet nous en prive sérieusement.

HTTP/S et WS/S

Le protocole WS est conçu pour utiliser la connexion HTTP courante, mais il est impossible de faire tourner de concert le serveur HTTP Ruby et le serveur WS Ratchet en écoute sur le même port. Hors, le seul port auquel nous ayons droit sur la cartouche est le port 8080. Une solution pourrait consister à installer une cartouche exposant un port supplémentaire, comme celle-ci. Pour cela, le fichier .openshift/action_hooks/start devrait être complété, ainsi que le fichier .openshift/action_hooks/stop, comme suit, en prenant soin d’adapter les ports d’écoute :

#!/bin/bash
# The logic to start up your application should be put in this
# script. The application will work only if it binds to
# $OPENSHIFT_DIY_IP:8080
nohup $OPENSHIFT_REPO_DIR/diy/testrubyserver.rb $OPENSHIFT_DIY_IP $OPENSHIFT_REPO_DIR/diy |& /usr/bin/logshifter -tag diy &
nohup php $OPENSHIFT_REPO_DIR/websockets/socket.php $OPENSHIFT_DIY_IP |& /usr/bin/logshifter -tag websockets &
#!/bin/bash
source $OPENSHIFT_CARTRIDGE_SDK_BASH

# The logic to stop your application should be put in this script.
if [ -z "$(ps -ef | grep testrubyserver.rb | grep -v grep)" ]
then
    client_result "Application testrubyserver.rb is already stopped"
else
    kill `ps -ef | grep testrubyserver.rb | grep -v grep | awk '{ print $2 }'` > /dev/null 2>&1
fi

if [ -z "$(ps -ef | grep socket.php | grep -v grep)" ]
then
    client_result "Application socket.php is already stopped"
else
    kill `ps -ef | grep socket.php | grep -v grep | awk '{ print $2 }'` > /dev/null 2>&1
fi
renald@venantvr:~$ sudo docker run --rm --name rhc -ti -v ~/.openshift:/root/.openshift -v ~/.ssh:/private bigm/rhc bash
[sudo] Mot de passe de renald : 
preparing ssh ...
root@ef5a9c263e67:~/.openshift/venantvr# rhc setup
OpenShift Client Tools (RHC) Setup Wizard

This wizard will help you upload your SSH keys, set your application namespace,
and check that other programs like Git are properly installed.

If you have your own OpenShift server, you can specify it now. Just hit enter to
use the server for OpenShift Online: openshift.redhat.com.
Enter the server hostname: |openshift.redhat.com| 

You can add more servers later using 'rhc server'.

Using an existing token for r.venant.valery@gmail.com to login to
openshift.redhat.com

Saving configuration to /root/.openshift/express.conf ... done

No SSH keys were found. We will generate a pair of keys for you.

    Created: /root/.ssh/id_rsa.pub

Your public SSH key must be uploaded to the OpenShift server to access code.
Upload now? (yes|no)
yes

  default (type: ssh-rsa)
  -----------------------
    Fingerprint: xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx

  rvenantvalery-ubuntu (type: ssh-rsa)
  ------------------------------------
    Fingerprint: xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx

  venantvr-ubuntu (type: ssh-rsa)
  ----------------------------
    Fingerprint: xx:xx:x:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx

You can enter a name for your key, or leave it blank to use the default name.
Using the same name as an existing key will overwrite the old key.

Provide a name for this key: |rvenantvaleryef5| venantvr-ubuntu

Key with the name 'venantvr-ubuntu' already exists. Updating ... 
done

Checking for git ... found git version 2.1.4

Checking common problems ...
done

Checking for a domain ... corbakatak

Checking for applications ... found 2

  php      http://php-corbakatak.rhcloud.com/
  venantvr http://venantvr-corbakatak.rhcloud.com/

  You are using 3 of 3 total gears
  The following gear sizes are available to you: small

Your client tools are now configured.
root@ef5a9c263e67:~/.openshift/venantvr# git add .
root@ef5a9c263e67:~/.openshift/venantvr# git commit -m "First commit..."
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
nothing to commit, working directory clean
root@ef5a9c263e67:~/.openshift/venantvr# git push
warning: push.default is unset; its implicit value has changed in
Git 2.0 from 'matching' to 'simple'. To squelch this message
and maintain the traditional behavior, use:

  git config --global push.default matching

To squelch this message and adopt the new behavior now, use:

  git config --global push.default simple

When push.default is set to 'matching', git will push local branches
to the remote branches that already exist with the same name.

Since Git 2.0, Git defaults to the more conservative 'simple'
behavior, which only pushes the current branch to the corresponding
remote branch that 'git pull' uses to update the current branch.

See 'git help config' and search for 'push.default' for further information.
(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode
'current' instead of 'simple' if you sometimes use older versions of Git)

Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 575 bytes | 0 bytes/s, done.
Total 6 (delta 4), reused 0 (delta 0)
remote: Stopping DIY cartridge
remote: CLIENT_RESULT: Application is already stopped
remote: PHP-FPM instance is stopped
remote: Building git ref 'master', commit a54fbd5
remote: Preparing build for deployment
remote: Deployment id is 9d0a2c4a
remote: Activating deployment
remote: Checking configuration
remote: - php-fpm.ini: No change
remote: - php-fpm.conf: No change
remote: - php.ini: No change
remote: [18-Aug-2017 15:51:35] NOTICE: [pool www] 'user' directive is ignored when FPM is not running as root
remote: [18-Aug-2017 15:51:35] NOTICE: [pool www] 'group' directive is ignored when FPM is not running as root
remote: PHP-FPM instance is started
remote: Starting DIY cartridge
remote: -------------------------
remote: Git Post-Receive Result: success
remote: Activation status: success
remote: Deployment completed with status: success
To ssh://5994a20789f5cf1fb100000c@venantvr-corbakatak.rhcloud.com/~/git/venantvr.git/
   8eb0f80..a54fbd5  master -> master

La structure de la solution, ainsi que le code source, sont disponibles ici.

En conclusion

Une seconde partie finalisera cet article, avec un moteur de chat.

À l’heure actuelle, OpenShift se concentre sur l’hébergement d’applications Web. Dans cet esprit, et pour essayer de fournir une certaine sécurité à vos applications, les seuls ports exposés au trafic entrant sont HTTP (80), HTTPS (443) et SSH (22). La plateforme fournit aussi un support WebSockets sur HTTP (8000) et HTTPS (8443).

Add a Comment

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

+ 6 = 15