1. Déploiement d'une instance GNU/Linux Debian 7 sous CloudStack

Mise à jour du code le 20/08/2013
  • Amélioration de la lecture du code et meilleure gestion d’erreur.
  • Moins de paramètres à renseigner.
  • Meilleure évolutivité pour les articles suivants.
Découvrez comment déployer une instance GNU/Linux Debian 7 sous CloudStack.

Dans un premier temps, l'utilisateur doit :
  • Disposer d’un réseau déjà déployé (pour les comptes Cloudstack Instance c’est normalement le cas).
  • Avoir la possibilité de créer une nouvelle instance (ne pas être à sa limite de création donc).
  • Déterminer l’ID du réseau auquel sera connecté son instance (via l’interface web ou l’API, de même, indications en commentaires dans le script).
  • Déterminer l’ID de l’offre de service (configuration CPU/RAM/Réseau) voulue, indications au sein du script.
Déploiement de l'API

<?php
	// Liste complète des appels possibles : http://download.cloud.com/releases/3.0.6/api_3.0.6/TOC_User.html

	################################
	# Paramètres généraux de l'API #
	################################
	
	// Renseignez ici votre clef API ainsi que votre clef secrète
	// Nous conseillons de déporter cette information au sein d'un autre fichier PHP dont on restreindra l'accès
	define("APIKEY","d1Ii4ON33yqUahgtlsjnzK0o7QXNzXZONZKcW_rDcOV4jln48b5ujsSvU-mo9KsoLbp631VyCI4fLb3u176JZg");
	define("SECRETKEY","fBenapO2hjOd3Vaj-NDmrekTIK85B8Jl_m8wFld4gi0XvLXy4w0iJz-2srztoVqkGVoblT25DyG4709gibo_TA");
	//On définit l'URL d'appel de l'API (ou 'EndPoint')
	define("ENDPOINT","https://cloudstack.ikoula.com/client/api");
	
	#############################################################
	# Paramètres utilisateur de configuration d(es) instance(s) #
	#############################################################
	
	//Première instance :
	
		/* UUID(s) du réseau auquel sera connectée votre instance.
		Utilisez la requête API 'listNetworks' ou l'interface pour lister les réseaux existants et déterminer le réseau voulu
		*/
		$vm01['conf']['networkid'] = "<ID DU RESEAU QUE VOUS SOUHAITEZ UTILISER>";
		
		/* UUID de l'offre de service (configuration matérielle) voulue.
		Utilisez la requête API 'listServiceOfferings' pour lister les offres existantes.
		Actuellement :
			- m1.small : c6b89fea-1242-4f54-b15e-9d8ec8a0b7e8
			- m1.medium : 8dae8be9-5dae-4f81-89d1-b171f25ef3fd
			- m1.large : d2b2e7b9-4ffa-419e-9ef1-6d413f08deab
			- m1.extralarge : 1412009f-0e89-4cfc-a681-1cda0631094b
		*/
		$vm01['conf']['serviceofferingid'] = "<ID DE L'OFFRE DE SERVICE A UTILISER>";

		
		/* UUID du modèle de systeme d'exploitation.
		Vous pouvez aussi utiliser la requête API 'listTemplates' pour lister les modèles existants.
		Dans notre exemple nous allons nous baser sur le template 'Debian 7 - Minimal - 64bits'
		*/
		$vm01['conf']['templateid'] = "6d5b069b-4268-4c7c-810f-2793c2ac44fb";
		
		/* Nom de votre choix pour l'instance (Nom de la machine et d'affichage dans l'interface)
		Caractères alphanumériques uniquement. Ce nom doit être unique au sein de votre réseau. */
		$vm01['conf']['hostname'] = "<NOM DE VOTRE INSTANCE>";
		
/* ------------------------------ NE PLUS RIEN MODIFIER APRES CETTE LIGNE ----------------------------------- */
	$json_response = null;

	################################################
	# Récupération de l'ID de zone pour ce network #
	################################################
	// Requête API
	$args['command'] = "listNetworks";

	// On reprend l'ID de réseau renseigné plus tôt
	$args['id'] = $vm01['conf']['networkid'];

	// Execution de la requête
	sendRequest($args, $json_response);

	// Stockage de l'ID de zone pour ce réseau
	$vm01['conf']['zoneid'] = $json_response['listnetworksresponse']["network"][0]['zoneid'];

	// On réinitialise les variables utilisées pour l'envoi de requêtes API
	unset($args);
	
	######################################################
	# Récupération de l'id de l'adresse IP de ce network #
	######################################################
	// Requête API
	$args['command'] = "listPublicIpAddresses";
	$args['associatednetworkid'] = $vm01['conf']['networkid'];

	// Execution de la requête
	sendRequest($args, $json_response);

	// Stockage de l'ID de l'adresse IP publique du réseau
	$vm01['conf']['publicIPid'] = $json_response['listpublicipaddressesresponse']["publicipaddress"][0]['id'];

	// On réinitialise les variables utilisées pour l'envoi de requêtes API
	unset($args);
		
	####################################
	# Création de la première instance #
	####################################
	
	// Requête API
	$args['command'] = "deployVirtualMachine";
	$args['zoneid'] = $vm01['conf']['zoneid'];
	$args['serviceofferingid'] = $vm01['conf']['serviceofferingid'];
	$args['templateid'] = $vm01['conf']['templateid'];
	$args['networkids'] = $vm01['conf']['networkid'];
	$args['name'] = $vm01['conf']['hostname']; //Hostname
	$args['displayname'] = $args['name']; //Nom d'affichage
	
	//Type de retour : JSON ou XML (par défaut : XML)
	$args['response'] = "json";

	//On réinitialise les variables avant l'appel API
	
	// Initialisation du client API
	sendRequest($args, $json_response);
	
	//On vérifie la presence d'un Job
	if(preg_match("/^[0-9a-f\-]+$/", $json_response['deployvirtualmachineresponse']['jobid']) > 0)
	{
		$jobs[] = $json_response['deployvirtualmachineresponse']['jobid'];
	}
	else{
		echo "ID de job non trouvé.\n";
	}
	
	//On mémorise la correspondance name/id de l'instance au sein d'un tableau
	$vm01['conf']['id'] = $json_response['deployvirtualmachineresponse']['id'];
	
	//On utilise la fonction de vérification des jobs asynchrones
	if(!checkJobs($jobs))
		exit;
	
/*-----------------------------------------------------------------------------------------------------------*/
	
	#############
	# Fonctions #
	#############
	
	//Fonction de gestion d'erreur(s) API
	function apiErrorCheck($json_response)
	{
		if(is_array($json_response))
		{
			$key = array_keys($json_response);
			if(isset($json_response['errorcode']))
			{
				echo "ERREUR : ".$json_response['errorcode']." - ".$json_response['errortext']."\n";
				exit;
			}
			if(isset($json_response['errorcode']) || (isset($key[0]) && isset($json_response[$key[0]]['errorcode'])))
			{
				echo "ERREUR : ".$json_response[$key[0]]['errorcode']." - ".$json_response[$key[0]]['errortext']."\n";
				exit;
			}
		}
		else
		{
			echo "ERREUR : PARAMETRE INVALIDE";
				exit;
		}
	}
	
	//Fonction d'envoi de requête à l'API
	function sendRequest($args, &$json_response)
	{
		$json_response = null;
		// Clef API
		$args['apikey'] = APIKEY;
		$args['response'] = "json";
		//On classe les paramètres
		ksort($args);
		// On construit la requête HTTP basée sur les paramètres contenus dans $args
		$query = http_build_query($args);
		// On s'assure de bien remplacer toutes les occurences de '+' par des '%20'
		$query = str_replace("+", "%20", $query);
		//On utilise la clef secrète et un algorithme HMAC SHA-1 sur la requête pour encoder la signature
		$hash = hash_hmac("SHA1",  strtolower($query), SECRETKEY, true);
		$base64encoded = base64_encode($hash);
		$signature = urlencode($base64encoded);

		// Construction de la requête finale sous la forme 'URL API + Requête API et paramètres + Signature'
		$query .= "&signature=" . $signature;

		// $jobs = null;
		
		// Initialisation du client API
		try
		{
			//Construction de la requête
			$httpRequest = new HttpRequest();
			$httpRequest->setMethod(HTTP_METH_POST);
			$httpRequest->setUrl(ENDPOINT . "?" . $query);
			
			// Envoi de la requête au serveur :
			$httpRequest->send();
			// Récupération du retour de l'API
			$response = $httpRequest->getResponseData();
			// retour de la réponse
			$json_response = json_decode($response['body'], true);
			
			apiErrorCheck($json_response);
		}
		catch (Exception $e)
		{
			echo "Probleme lors de l'envoi de la requête. ERREUR=".$e->getMessage();
			exit;
		}
	}
	
	//Fonction de vérification des jobs asynchrones
	function checkJobs($jobs)
	{
		$json_response = null;
		$error_msg = "";
		if(is_array($jobs) && count($jobs) > 0)
		{
			// La tâche est asynchrone, on doit donc régulièrement vérifier les tâches avec une sécurité
			$secu = 0;
			// On indexe les tâches
			$ij = 0;
			// Tant qu'il y a des tâches asynchrones non-terminées dans la pile de vérification, on boucle et on vérifie le statut
			while(count($jobs) > 0 && $secu < 100)
			{
				try
				{
					//On interroge le statut de la tâche asynchrone
					// http://download.cloud.com/releases/3.0.6/api_3.0.6/root_admin/queryAsyncJobResult.html
					$args['apikey'] = APIKEY;
					$args['command'] = "queryAsyncJobResult";
					$args['jobid'] = $jobs[$ij];
					$args['response'] = "json";
					
					$json_response = null;
					sendRequest($args, $json_response);
					
					if(is_array($json_response['queryasyncjobresultresponse']))
					{
						// Si OK...
						if($json_response['queryasyncjobresultresponse']['jobstatus'] == 1)
						{
							// ...On retire simplement la tâche du tableau a surveiller
							//return("JOB OK\n");
							array_splice($jobs, $ij, 1);
						}
						// Sinon...
						elseif($json_response['queryasyncjobresultresponse']['jobstatus'] == 2)
						{
							//...On mémorise l'erreur et on retire la tâche du tableau à surveiller
							//return("JOB ERREUR\n");
							array_splice($jobs, $ij, 1);
							$error_msg .= "ERREUR ! RESULT_CODE=".$json_response['queryasyncjobresultresponse']['jobresultcode'];
						}
						// Cette tâche est encore en cours, on passe à la suivante et on temporise
						elseif($json_response['queryasyncjobresultresponse']['jobstatus'] == 0)
						{
							// Tâche suivante
							$ij++;
							// Temporisation entre chaque interrogation pour ne pas charger inutilement l'API
							sleep(5);
						}
					}
				}
				catch(Exception $e)
				{
					$error_msg .= "EXCEPTION Lors de la verification la tache asynchrone. JOB_UUID:".$jobs[$ij]." ERREUR=".$e->getMessage()." \n";
				}

				// Si l'index arrive en bout de tableau, on le réinitialise
				if($ij == count($jobs))
				{
					$ij = 0;
					$secu++;
				}
			}

			if($error_msg)
			{
				echo "ERRORS:".$error_msg."\n";
				return false;
			}
			return true;
		}
		echo "No job\n";
		return false;
	}
?>

Le principal intérêt des opérations via l'API est la rapidité d’exécution et la flexibilité des procédés.

L’interface CloudStack a comme avantages d’être ergonomique et à la portée de la majorité mais l’API va encore plus loin et permet des déploiements rapides et automatisés d’une ou plusieurs instances prêtes à l’emploi.

Ce script vous permettra donc de déployer de manière programmatique une instance Debian « Wheezy » prête à l’emploi. La version fournie ici en exemple est très simplifiée mais il est tout à fait possible via quelques modifications mineures de procéder à la création d’instances d’autres configurations « matérielles » (Mémoire/Processeur/Réseau en changeant d’offre de service) ou logicielles (via la modification du modèle choisi pour un autre système d’exploitation).

De même, cet aspect programmatique permet la création multiple d’instances sans intervention de l’utilisateur (en faisant en sorte de changer automatiquement le nom pour chaque instance).

Via la répétition de son exécution on peut donc imaginer déployer des clusters complets d’instances identiques en un temps très réduit.

De plus, l’exemple ci-dessus est fourni sur la base du langage PHP5 mais il est tout à fait possible d’utiliser d’autres langages de programmation selon vos préférences ou contraintes.

Plus d’exemples à venir…


Ajouter un commentaire