Découvrez comment créer une VM sous Debian à partir d’une image au travers de l’API CloudStack
L'article précédent vous a présenté comment déployer simplement une instance dans un environnement CloudStack, aujourd’hui nous poussons les possibilités un peu plus loin et augmentons quelque peu la technicité puisque nous y intégrons une gestion du réseau. L’aspect sécurité est omniprésent au sein de votre infrastructure virtuelle CloudStack : la politique de base est très stricte, rien n’entre et rien ne sort par défaut. Il vous incombe donc de procéder à l’autorisation des flux sortants depuis vos réseaux virtuels ainsi que des flux entrants à destination de vos instances.
La plupart d’entre vous disposent, à titre professionnel ou personnel, d’un accès ADSL ou Fibre via (pour la plupart) une « Box » fournie par votre opérateur derrière laquelle se trouvent vos postes personnels/professionnels ; tablettes ; smartphones … en adressage privé (non joignable directement depuis Internet).
Les réseaux CloudStack fonctionnent un peu de la même manière : toutes vos instances disposent d’adresses privées (non-joignables depuis Internet) et chacun de vos réseaux dispose d’un routeur virtuel (comparable à une Box ADSL/Fibre) par laquelle vos instances doivent passer pour sortir et au travers de laquelle les machines de l’extérieur doivent transiter pour joindre vos instances.
Le script fourni ci-dessous permet donc, en plus de la création initiale de l’instance abordée lors du précédent article, de créer des règles pour permettre à vos instances de sortir pour quatres usages : le PING, le HTTP (navigation web), le HTTPS (navigation web sécurisée) et le DNS (requêtes de correspondances IP <-> Noms de domaines).
De plus, nous ouvrons la connexion entrante sur le port 2222 (choisi arbitrairement) que nous faisons correspondre sur le port 22 (SSH, pour la console à distance) de notre instance.
De cette manière, en nous connectant à notre IP publique en SSH sur le port 2222 nous accéderons à notre instance qui, elle-même, pourra naviguer sur Internet via les ouvertures effectuées en sortie.
Déploiement de l'API
(les prérequis sont les mêmes que pour l'article précédent "Déploiement d'une instance GNU/LINUX Debian 7 sous CloudStack")
Dans un premier temps, l'utilisateur doit :
Prochainement "Comment créer une VM sous Debien avec un MySQL préinstallé"
L'article précédent vous a présenté comment déployer simplement une instance dans un environnement CloudStack, aujourd’hui nous poussons les possibilités un peu plus loin et augmentons quelque peu la technicité puisque nous y intégrons une gestion du réseau. L’aspect sécurité est omniprésent au sein de votre infrastructure virtuelle CloudStack : la politique de base est très stricte, rien n’entre et rien ne sort par défaut. Il vous incombe donc de procéder à l’autorisation des flux sortants depuis vos réseaux virtuels ainsi que des flux entrants à destination de vos instances.
La plupart d’entre vous disposent, à titre professionnel ou personnel, d’un accès ADSL ou Fibre via (pour la plupart) une « Box » fournie par votre opérateur derrière laquelle se trouvent vos postes personnels/professionnels ; tablettes ; smartphones … en adressage privé (non joignable directement depuis Internet).
Les réseaux CloudStack fonctionnent un peu de la même manière : toutes vos instances disposent d’adresses privées (non-joignables depuis Internet) et chacun de vos réseaux dispose d’un routeur virtuel (comparable à une Box ADSL/Fibre) par laquelle vos instances doivent passer pour sortir et au travers de laquelle les machines de l’extérieur doivent transiter pour joindre vos instances.
Le script fourni ci-dessous permet donc, en plus de la création initiale de l’instance abordée lors du précédent article, de créer des règles pour permettre à vos instances de sortir pour quatres usages : le PING, le HTTP (navigation web), le HTTPS (navigation web sécurisée) et le DNS (requêtes de correspondances IP <-> Noms de domaines).
De plus, nous ouvrons la connexion entrante sur le port 2222 (choisi arbitrairement) que nous faisons correspondre sur le port 22 (SSH, pour la console à distance) de notre instance.
De cette manière, en nous connectant à notre IP publique en SSH sur le port 2222 nous accéderons à notre instance qui, elle-même, pourra naviguer sur Internet via les ouvertures effectuées en sortie.
Déploiement de l'API
(les prérequis sont les mêmes que pour l'article précédent "Déploiement d'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.
<?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; /*-------------------------------- 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; } ?>
Prochainement "Comment créer une VM sous Debien avec un MySQL préinstallé"