Ajouter un commentaire

2.0 Déploiement et configuration réseau (firewall) d’une instance CloudStack avec Apache préinstallé.


Découvrez comment créer une VM sous Debian avec Apache préinstallé.
  • Installation depuis une image Debian 7 au travers d'une seule API CloudStack.
  • Installation d'Apache au travers de l'API Puppet.
(Les prérequis sont les mêmes que pour les deux premiers articles de la série).

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.

Le premier article vous a présenté comment déployer simplement une instance dans un environnement CloudStack, le second comment la configurer d’un point de vue réseau pour qu’elle soit accessible depuis l’extérieur.

Aujourd’hui nous nous concentrerons sur la partie logicielle « interne » à cette instance, en procédant de manière automatisée à l’installation de l’applicatif « Apache » en version 2.2 (dernière version supportée sous Debian 7).
Nos équipes Recherche & Développement ont tout fait pour vous simplifier le travail si bien que vous serez étonné du peu d’opérations à effectuer afin de parvenir à installer un environnement Apache 2.2 (avec support PHP 5) de manière automatique.

Pour ceux d’entre vous qui ont découvrent nos articles aujourd’hui, vous pouvez exécuter l’ensemble du script suivant afin de déployer automatiquement l’ensemble du contenu des deux précédents articles ainsi que de celui du jour :

[Code]

<?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","<VOTRE CLEF API>");
	define("SECRETKEY","<VOTRE CLEF SECRETE>");
	//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'] = "718952be-d4f5-408c-991b-f211ee4f420b";
		
		/* 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>";
		
		/* Données utilisateur a passer au processus de déploiement de l'instance.
		Ce paramètre nous sert à passer l'URL du script de déploiement du module "Apache" à "Puppet".
		Il est vous est tout à fait possible de créer votre propre script Puppet et de le rendre disponible en HTTP. */
		$vm01['conf']['userdata'] = "http://puppetrepository.ikoula.com/apache/install.sh";
		
/* ------------------------------ 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
	$args['userdata'] = base64_encode($vm01['conf']['userdata']); // Données utilisateur
	
	//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;
		
/*-------------------------------- Création des règles de sortie --------------------------------*/
	// On réinitialise les variables utilisées pour l'envoi de requêtes API
	unset($args);
	
	################
	# Ping sortant #
	################
	$args['command'] = "createEgressFirewallRule";
	$args['networkid'] = $vm01['conf']['networkid'];
	
	// Définition du protocole a filtrer ici ICMP (utiliser pour les ping), les protocole sont ICMP/UDP/TCP
	$args['protocol'] = "ICMP";
	// Définition du masque réseau qui nous permet d'autoriser certainne IP,
	// ici nous acceptons "le ping" vers toute les IPs
	$args['cidrlist'] = "0.0.0.0/0";
	// On définit le type ICMP le code 8 et une demande d'ECHO (echo-request) cela permet le ping
	$args['icmptype'] = 8;
	
	// On définit le code ICMP le code 0 doit être défini à 0 pour l'utilisation du type 8 pour l'ICMP
	$args['icmpcode'] = 0;
	
	// Execution de la requête
	sendRequest($args, $json_response);

	// Traitement de la requête
	if(preg_match("/^[0-9a-f\-]+$/", $json_response['createegressfirewallruleresponse']['jobid']) > 0)
	{
		$jobs[] = $json_response['createegressfirewallruleresponse']['jobid'];
	}
	else{
		echo "ID de job non trouvé.\n";
	}
	
	if(!checkJobs($jobs))
		exit;
	
	// On réinitialise les variables utilisées pour l'envoi de requêtes API
	unset($args);
	
	##########################
	# Protocole HTTP sortant #
	##########################
	$args['command'] = "createEgressFirewallRule";
	$args['networkid'] = $vm01['conf']['networkid'];
	
	// Définition du protocole a filtrer ici TCP (utiliser par le protocole HTTP), les protocole sont ICMP/UDP/TCP
	$args['protocol'] = "TCP";
	
	// Définition du masque réseau qui nous permet d'autoriser certainne IP,
	// ici nous accepton la connection vers toute les IP
	$args['cidrlist'] = "0.0.0.0/0";
	
	// Le port de début (port 80 port standart pour le HTTP)
	$args['startport'] = 80;
	
	// Le port de fin (port 80 port standart pour le HTTP)
	$args['endport'] = 80;
	
	// Execution de la requête
	sendRequest($args, $json_response);

	// Traitement de la requête
	if(preg_match("/^[0-9a-f\-]+$/", $json_response['createegressfirewallruleresponse']['jobid']) > 0)
	{
		$jobs[] = $json_response['createegressfirewallruleresponse']['jobid'];
	}
	else{
		echo "ID de job non trouvé.\n";
	}
	
	// On réinitialise les variables utilisées pour l'envoi de requêtes API
	unset($args);
	
	###########################
	# Protocole HTTPS sortant #
	###########################
	// Requête API
	$args['command'] = "createEgressFirewallRule";
	$args['networkid'] = $vm01['conf']['networkid'];
	
	// Définition du protocole a filtrer ici TCP (utiliser par le protocole HTTPS), les protocole sont ICMP/UDP/TCP
	$args['protocol'] = "TCP";
	
	// Définition du masque réseau qui nous permet d'autoriser certainne IP,
	// ici nous accepton la connection vers toute les IP
	$args['cidrlist'] = "0.0.0.0/0";
	
	// Le port de début (port 443 port standart pour le HTTPs)
	$args['startport'] = 443;
	
	// Le port de fin (port 443 port standart pour le HTTPs)
	$args['endport'] = 443;
	
	// Execution de la requête
	sendRequest($args, $json_response);

	// Traitement de la requête
	if(preg_match("/^[0-9a-f\-]+$/", $json_response['createegressfirewallruleresponse']['jobid']) > 0)
	{
		$jobs[] = $json_response['createegressfirewallruleresponse']['jobid'];
	}
	else{
		echo "ID de job non trouvé.\n";
	}
	
	// On réinitialise les variables utilisées pour l'envoi de requêtes API
	unset($args);

	
	#########################
	# Protocole DNS sortant #
	#########################
	// Requête API
	$args['command'] = "createEgressFirewallRule";
	$args['networkid'] = $vm01['conf']['networkid'];
	
	// Définition du protocole a filtrer ici UDP (utiliser par le protocole DNS), les protocole sont ICMP/UDP/TCP
	$args['protocol'] = "UDP";
	
	// Définition du masque réseau qui nous permet d'autoriser certainne IP,
	// ici nous accepton la connection vers toute les IP
	$args['cidrlist'] = "0.0.0.0/0";
	
	// Le port de début (port 443 port standart pour le HTTPs)
	$args['startport'] = 53;
	
	// Le port de fin (port 443 port standart pour le HTTPs)
	$args['endport'] = 53;
	
	// Execution de la requête
	sendRequest($args, $json_response);
	
	// Traitement de la requête
	if(preg_match("/^[0-9a-f\-]+$/", $json_response['createegressfirewallruleresponse']['jobid']) > 0)
	{
		$jobs[] = $json_response['createegressfirewallruleresponse']['jobid'];
	}
	else{
		echo "ID de job non trouvé.\n";
	}
	
	// On réinitialise les variables utilisées pour l'envoi de requêtes API
	unset($args);
	
	########################################
	# Ouverture du port public pour le SSH #
	########################################
	// Requête API
	$args['command'] = "createFirewallRule";
	$args['ipaddressid'] = $vm01['conf']['publicIPid'];
	
	// Définition du protocole a filtrer ici TCP (utiliser par le protocole SSH), les protocole sont ICMP/UDP/TCP
	$args['protocol'] = "TCP";
	
	// Définition du masque réseau qui nous permet d'autoriser certainne IP,
	// ici nous accepton la connection vers toute les IP
	$args['cidrlist'] = "0.0.0.0/0";
	
	// Le port de début (port 2222 port qui sera plus tard rediriger vers le port 22(ssh) de votre VM)
	$args['startport'] = 2222;
	
	// Le port de fin (port 2222 port qui sera plus tard rediriger vers le port 22(ssh) de votre VM)
	$args['endport'] = 2222;
	
	// Execution de la requête
	sendRequest($args, $json_response);
	
	// Traitement de la requête
	if(preg_match("/^[0-9a-f\-]+$/", $json_response['createfirewallruleresponse']['jobid']) > 0)
	{
		$jobs[] = $json_response['createfirewallruleresponse']['jobid'];
	}
	else{
		echo "ID de job non trouvé.\n";
	}
	
	// On réinitialise les variables utilisées pour l'envoi de requêtes API
	unset($args);
	
	###################################
	# Redirection de port pour le SSH #
	###################################
	// Requête API
	$args['command'] = "createPortForwardingRule";
	$args['ipaddressid'] = $vm01['conf']['publicIPid'];
	$args['virtualmachineid'] = $vm01['conf']['id'];
	
	// Définition du protocole a filtrer ici TCP (utiliser par le protocole SSH), les protocole sont ICMP/UDP/TCP
	$args['protocol'] = "TCP";

	// Le Port public sur lequels vous allez initialiser la connection et définie plus tôt comme ouvert
	$args['publicport'] = 2222;
	
	// Le SSH de votre VM
	$args['privateport'] = 22;
	
	// Execution de la requête
	sendRequest($args, $json_response);
	
	// Traitement de la requête
	if(preg_match("/^[0-9a-f\-]+$/", $json_response['createportforwardingruleresponse']['jobid']) > 0)
	{
		$jobs[] = $json_response['createportforwardingruleresponse']['jobid'];
	}
	else{
		echo "ID de job non trouvé.\n";
	}
	
	// On réinitialise les variables utilisées pour l'envoi de requêtes API
	unset($args);
	
	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;
	}
?>


Dans un cas comme dans l’autre l’article d’aujourd’hui vous montre la simplicité d’utilisation de l’applicatif Puppet qui permet l’installation et la configuration de services sur des instances GNU/Linux.
Nous couplons donc les possibilités des deux APIs (CloudStack et Puppet) afin que CloudStack transmette, lors du déploiement de votre instance, l’adresse d’un script qui sera exécuté sur votre instance.

Ce script a pour fonction de rapatrier fichiers du module Puppet correspondant et de les passer à Puppet pour installation.
Ce dernier est d’ailleurs consultable ici : http://puppetrepository.ikoula.com/apache/install.sh

Comme vous pourrez le constater si vous avez eu la curiosité de cliquer sur ce lien, le script est assez basique puisqu’il indique seulement à l’installation de Puppet présente dans votre instance d’installer le module « apache » qui a été rapatrié dans un répertoire « /etc/puppet/ikoula ».

Il vous est tout à fait possible de créer vos propres scripts via des documentations que nous fournirons à l’avenir puisque vous avez la main sur l’adresse du script que vous renseignez lors du déploiement de l’instance via l’API.

Prochainement : "Comment créer une VM sous Debian avec MySQL préinstallé".