Vous le savez, Windows Azure est une plateforme qui permet de déployer tous types de technologies; nous vous parlons souvent de Java et de PHP, mais aujourd’hui je voudrais vous montrer comment déployer facilement une application Ruby dans Windows Azure. Cela nous donnera l’occasion de voir quelques petits utilitaires et astuces utiles pour automatiser le déploiement d’applications dans des rôles Windows Azure avec les Startup Tasks.

Tout d’abord, un petit mot à propos du support de la plateforme Windows Azure en Ruby: je vais utiliser la librairie WAZ-Storage qui permet d’accéder aux Blobs, Tables et Queues Windows Azure depuis une application Ruby. Cela nous permettra donc d’exécuter notre application dans le nuage, et de bénéficier également du stockage non-relationnel.

Pour partir sur l’application la plus compacte possible, je vais partir sur le framework Sinatra qui permet de développer des applications Web de façon très concise; voici la petite application de test que je vais déployer:

require 'rubygems'
require 'sinatra'
require 'waz-blobs'
require 'kconv'
get '/' do
	WAZ::Storage::Base.establish_connection!(:account_name => 'foo', :access_key => 'bar', :use_ssl => true)
	c = WAZ::Blobs::Container.find('images')
	blobnames = c.blobs.map { |blob| blob.name }
	blobnames.join("<br/>")
end

Quelques points notables de cette superbe application:

  • Il s’agit d’une application de test qui tournera dans la configuration “développement” de Sinatra, et non dans une configuration de production… Mais cela suffira pour la démonstration!
  • Le fonctionnement de la librairie WAZ-Storage est très simple: remplacez simplement le “foo/bar” dans establish_connection() par votre compte et votre clef, puis choisissez un nom de container qui existe pour l’appel à Container.find()
  • Le code assemble ensuite les noms des Blobs listés en une chaîne de caractères pour l’afficher dans la page
  • La librairie WAZ-Storage expose bien entendu une API beaucoup plus riche, permettant de créer, modifier, détruire des Blobs et des Containers… Vous pouvez aller sur le Github pour télécharger la librairie ou même contribuer ;-)

Voyons donc comment préparer tout l’environnement pour aller exécuter cette application dans Windows Azure.

Nous allons utiliser un Worker Role pour exécuter l’interpréteur Ruby. L’avantage de Ruby et du framework Sinatra est que leur empreinte sur le système est très légère: on a simplement à installer l’interpréteur, télécharger quelques Gems et on a tout ce qu’il faut. C’est donc un scénario parfait pour utiliser des Startup Tasks dans un Worker Role, ce qui nous permettra de continuer de bénéficier des avantages du Platform As A Service (mise à jour automatique du système).

J’ai donc créé un projet de type Cloud Service dans Visual Studio 2010, dans lequel j’ai mis un Worker Role. J’ai ensuite créé dans le projet WorkerRole un répertoire “startup” dans lequel je range tous les fichiers nécessaires pour automatiser l’installation de Ruby. Cela donne ceci:

image

Voyons donc l’utilité de tous ces fichiers, dans l’ordre de leur utilisation dans le script de démarrage:

ruby-1.9.2-p180-i386-mingw32.7z est l’interpréteur proprement dit. J’ai choisi la version RubyInstaller for Windows, qui est prête à l’emploi. J’ai choisi la version 7-Zip plutôt que l’installeur, car cela simplifie l’opération (il suffit d’extraire le contenu de l’archive). J’aurais pu envisager de télécharger l’archive dynamiquement au démarrage (voir ci-après), mais l’installeur faisant à peine 6 Mo, cela ne valait pas le coup!

test.rb est évidemment mon “application” de test Ruby

curl est un utilitaire en ligne de commande très répandu, permettant de télécharger des ressources accessibles par des URL (typiquement du HTTP, mais pas seulement). Je ne l’utilise pas pour l’instant, mais il peut rendre de grands services dans une Startup Task: pour télécharger l’interpréteur, ou des composants additionnels, depuis leur site d’hébergement ou depuis le Blob Storage. C’est typiquement ce que j’utiliserais pour des composants plus volumineux, comme un JDK. J’ai choisi le binaire Windows 64-bit sans SSL.

7za est bien entendu la version ligne de commande de 7-Zip, le meilleur archiveur de la planète. Cette version se trouve sur la page des téléchargements.

WAUtils va être utilisé dans le script de démarrage pour trouver l’emplacement du stockage local alloué par Windows Azure, dans lequel on va installer l’interpréteur Ruby. Il n’y a en effet pas de moyen de “deviner” ce chemin; le source de WAUtils est extrêmement simple, je l’ai compilé dans un projet à part et simplement ajouté l’exécutable dans le répertoire “startup” de mon projet.

namespace WAUtils
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length < 2)
            {
                System.Console.Error.WriteLine("usage: WAUtils [GetLocalResource <resource name>|GetIPAddresses <rolename>]");
                System.Environment.Exit(1);
            }
            switch (args[0])
            {
                case "GetLocalResource":
                    GetLocalResource(args[1]);
                    break;
                case "GetIPAddresses":
                    break;
                default:
                    System.Console.Error.WriteLine("unknown command: " + args[0]);
                    System.Environment.Exit(1);
                    break;
            }
        }
        static void GetLocalResource(string arg)
        {
            LocalResource res = RoleEnvironment.GetLocalResource(arg);
            System.Console.WriteLine(res.RootPath);
        }
    }
}

Comme vous le voyez c’est simplissime, j’utilise essentiellement la méthode RoleEnvironment.GetLocalResource() pour trouver l’emplacement de mon répertoire de stockage.

Voyons maintenant le fichier ServiceDefinition.csdef de ma solution; j’y trouve la définition de cet emplacement de stockage, que j’ai nommé “Ruby” et pour lequel je demande 1 Go. Je sais d’expérience que cet espace se trouvera situé sur le disque C: de la machine virtuelle dans Windows Azure, mais je ne peux bien entendu pas me fier à cette information, d’où l’utilisation du GetLocalResource().

<LocalResources>
  <LocalStorage name="Ruby" cleanOnRoleRecycle="false" sizeInMB="1024" />
</LocalResources>

Je vais ensuite aller ajouter deux Startup Tasks:

<Startup>
  <Task commandLine="startup\installruby.cmd" executionContext="elevated" />
  <Task commandLine="startup\startruby.cmd" taskType="background" />
</Startup>
  • installruby.cmd va s’exécuter en mode Administrateur (même si cela n’est pas réellement nécessaire dans ce cas, mais c’est toujours plus sûr pour des opérations de configuration du système…)
  • startupruby.cmd va réellement démarrer l’interpréteur, tournera en mode non-Administrateur (pour des raisons de sécurité), et en mode “background”, ce qui signifie que le Worker Role n’attendra pas la fin de la tâche pour finir de démarrer.

Voyons maintenant le contenu de ces deux scripts!

Tout d’abord, installruby.cmd:

cd %~dp0
for /f %%p in ('WAUtils.exe GetLocalResource Ruby') do set RUBYPATH=%%p
echo y| cacls %RUBYPATH% /grant everyone:f /t
7za x ruby-1.9.2-p180-i386-mingw32.7z -y -o"%RUBYPATH%"
set PATH=%PATH%;%RUBYPATH%\ruby-1.9.2-p180-i386-mingw32\bin
call gem install sinatra haml waz-storage --no-ri --no-rdoc
exit /b 0

On est sur du script classiques, voici les différentes étapes:

  • On lance WAUtils.exe pour récupérer le chemin de l’espace disque local, et on le stocke dans une variable RUBYPATH
  • On utilise la commande Windows cacls.exe pour donner tous les droits à ce répertoire
  • On extrait l’archive 7-Zip avec 7za, dans le répertoire RUBYPATH
  • On ajoute le sous-répertoire “bin” de Ruby au PATH système
  • Ensuite, on utilise la commande “gem” pour télécharger dynamiquement les librairies Ruby dont nous avons besin

Et finalement, startupruby.cmd qui va me lancer mon application:

for /f %%p in ('startup\WAUtils.exe GetLocalResource Ruby') do set RUBYPATH=%%p
set PATH=%PATH%;%RUBYPATH%\ruby-1.9.2-p180-i386-mingw32\bin
ruby test.rb

C’est évidemment la ligne “ruby test.rb” qui exécute mon application.

Voilà, vous avez tous les éléments en mains pour re-créer votre propre service Ruby dans Windows Azure! Je vous proposerai dans un prochain billet de télécharger une version prête à l’emploi de ce package Ruby, exploitable directement et sans passer nécessaire par Visual Studio…