ℹ️ À savoir · En bref

Le provider officiel citrix/citrix pilote un site Citrix DaaS ou CVAD on-premises (2311 et plus, avec Web Studio) comme du code : connexions d'hébergement, pools de ressources, catalogues MCS, groupes de mise à disposition, applications, stratégies…

Bénéfices : un environnement reproductible (recette = production), chaque changement passe par un plan relu, et la mise à jour d'image devient une simple modification de variable.

Objectifs de la fiche

  • Configurer le provider pour Citrix Cloud ou un DDC on-premises.
  • Comprendre la chaîne d'objets : zone → connexion → pool → catalogue → groupe de mise à disposition.
  • Écrire un catalogue MCS et un groupe de mise à disposition, puis mettre à jour l'image.
  • Reprendre un site existant sous Terraform sans tout recréer.

Le modèle d’objets

Chaque objet Studio a sa ressource Terraform ; les références créent l'ordre de création
Chaque objet Studio a sa ressource Terraform ; les références créent l'ordre de création
Objet StudioRessource Terraform
Zone / emplacement de ressourcescitrix_zone
Connexion d'hébergementcitrix_azure_hypervisor, citrix_vsphere_hypervisor, citrix_xenserver_hypervisor, citrix_nutanix_hypervisor…
Ressources (réseau, stockage)citrix_azure_hypervisor_resource_pool (et équivalents par hyperviseur)
Catalogue de machinescitrix_machine_catalog (MCS, PVSStreaming ou Manual, Remote PC)
Groupe de mise à dispositioncitrix_delivery_group
Compte de service (jonction AD / Entra)citrix_service_account

Configurer le provider

providers.tf

terraform {
  required_providers {
    citrix = {
      source  = "citrix/citrix"
      version = ">= 1.0"          # fige la version testée dans ton projet
    }
  }
}

# Citrix Cloud (DaaS) : client API sécurisé créé dans Citrix Cloud
provider "citrix" {
  cvad_config = {
    customer_id   = var.customer_id      # ou variable d'env. CITRIX_CUSTOMER_ID
    client_id     = var.client_id        # ou CITRIX_CLIENT_ID
    client_secret = var.client_secret    # ou CITRIX_CLIENT_SECRET
  }
}

# CVAD on-premises (≥ 2311, Web Studio installé)
# provider "citrix" {
#   cvad_config = {
#     hostname      = "ddc01.corp.local"       # ou CITRIX_HOSTNAME
#     client_id     = "CORP\\svc-terraform"    # compte admin Citrix
#     client_secret = var.ddc_password
#   }
# }

⚠️ Piège

Ne mets jamais le client_secret dans un fichier commité : passe-le par les variables d'environnement CITRIX_* ou un coffre (Key Vault, variable secrète de pipeline). Et souviens-toi qu'il peut finir dans le state : backend chiffré obligatoire (voir TF-01).

Connexion d’hébergement et pool de ressources (Azure)

hosting.tf

resource "citrix_azure_hypervisor" "azure" {
  name                = "Azure-FranceCentral"
  zone                = var.zone_id
  active_directory_id = var.azure_tenant_id
  subscription_id     = var.azure_subscription_id
  authentication_mode = "SystemAssignedManagedIdentity"   # pas de secret à gérer
  proxy_hypervisor_traffic_through_connector = true
}

resource "citrix_azure_hypervisor_resource_pool" "vda" {
  name                           = "RP-VDA-FranceCentral"
  hypervisor                     = citrix_azure_hypervisor.azure.id
  region                         = "France Central"
  virtual_network_resource_group = "rg-prd-network"
  virtual_network                = "vnet-prd-citrix"
  subnets                        = ["snet-vda"]
}

Le catalogue MCS

catalog.tf

resource "citrix_machine_catalog" "w2025_ms" {
  name              = "CAT-W2025-MS"
  description       = "Windows Server 2025 multi-session"
  zone              = var.zone_id
  allocation_type   = "Random"          # obligatoire en MultiSession
  session_support   = "MultiSession"
  provisioning_type = "MCS"

  provisioning_scheme = {
    hypervisor               = citrix_azure_hypervisor.azure.id
    hypervisor_resource_pool = citrix_azure_hypervisor_resource_pool.vda.id
    identity_type            = "ActiveDirectory"   # ou AzureAD, HybridAzureAD
    machine_domain_identity = {
      domain             = "corp.local"
      domain_ou          = "OU=VDA,OU=Citrix,DC=corp,DC=local"
      service_account_id = citrix_service_account.ad_join.id   # évite le mot de passe en clair
    }
    azure_machine_config = {
      service_offering  = "Standard_D8s_v5"
      storage_type      = "Premium_LRS"
      use_managed_disks = true
      license_type      = "Windows_Server"
      azure_master_image = {
        resource_group = "rg-prd-images"
        master_image   = var.master_snapshot          # la variable qu'on fait évoluer
      }
    }
    number_of_total_machines = 4
    machine_account_creation_rules = {
      naming_scheme      = "VDA-MS-##"
      naming_scheme_type = "Numeric"
    }
  }
}
AttributÀ retenir
allocation_type / session_supportRandom + MultiSession pour du partagé ; Static + SingleSession pour des bureaux attribués
identity_typeActiveDirectory, AzureAD (exige network_mapping et machine_profile) ou HybridAzureAD
number_of_total_machinesAugmenter = ajouter des VM ; diminuer = suppression (sans session active, statiques non attribuées)
enroll_in_intuneInscription Intune des VDA, uniquement avec identity_type = "AzureAD" (voir M365-INT-01)
delete_virtual_machines / delete_machine_accountsComportement au destroy : effectif seulement après un apply qui l'a enregistré

Le groupe de mise à disposition

delivery_group.tf

resource "citrix_delivery_group" "bureaux" {
  name = "DG-W2025-Bureaux"
  associated_machine_catalogs = [{
    machine_catalog = citrix_machine_catalog.w2025_ms.id
    machine_count   = 4
  }]

  desktops = [{
    published_name = "Bureau Windows"     # le renommer recrée le bureau !
    enabled        = true
    restricted_access_users = {
      allow_list = ["CORP\\GG-Citrix-Bureau"]
    }
  }]

  autoscale_settings = {
    autoscale_enabled = true
    timezone          = "Romance Standard Time"
    peak_buffer_size_percent = 10
    power_time_schemes = [{
      display_name          = "Semaine"
      days_of_week          = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
      peak_time_ranges      = ["08:00-18:00"]
      pool_using_percentage = false
      pool_size_schedules   = [{ time_range = "07:30-18:30", pool_size = 2 }]
    }]
  }

  reboot_schedules = [{
    name                    = "Reboot hebdo"
    reboot_schedule_enabled = true
    frequency               = "Weekly"
    frequency_factor        = 1
    days_in_week            = ["Sunday"]
    start_date              = "2026-11-01"
    start_time              = "03:00"
    ignore_maintenance_mode = true
    natural_reboot_schedule = false
    reboot_duration_minutes = 60
  }]
}

⚠️ Piège

restricted_access_users omis = tous les utilisateurs authentifiés ont accès ; défini à {} = personne n'a accès.

Changer published_name d'un bureau le recrée : les sessions déconnectées sur l'ancien bureau ne pourront pas être reprises. Fais-le hors des heures de production.

Mettre à jour l’image

La mise à jour d'image devient une modification de variable relue dans un plan
La mise à jour d'image devient une modification de variable relue dans un plan
  • Préparer la nouvelle image (Ansible, Packer ou à la main), prendre un snapshot.
  • Changer master_snapshot dans le .tfvars, puis plan : on doit voir ~ update in-place sur le catalogue, jamais -/+ replace.
  • image_update_reboot_options (dans azure_machine_config) règle le redémarrage : reboot_duration = -1 pour appliquer au prochain arrêt, sinon cycle de redémarrage avec message d'avertissement.
  • Retour arrière = remettre l'ancien nom de snapshot et ré-appliquer.

⚠️ Piège

apply_updates_to_existing_machines = true synchronise et redémarre toutes les VM allumées à chaque mise à jour du catalogue, sessions comprises. À activer pour un apply ponctuel, puis à remettre à false.

Reprendre un site existant

BesoinSolution
Générer le code d'un site existantScript onboarding helper du dépôt GitHub du provider (scripts/onboarding-helper), filtrable par type et par nom
Importer un objet précisterraform import citrix_delivery_group.bureaux <GUID> ou bloc import { } (Terraform ≥ 1.5)
Retrouver les GUIDScript object-ids-helper du même dépôt, ou les traces réseau de Web Studio
Vérifier l'importterraform plan doit afficher No changes avant toute modification

Pièges classiques

SymptômeCauseParade
Le plan veut recréer le catalogueChangement d'un attribut non modifiable (identité, type, lettre du cache…)Lire forces replacement ; créer un nouveau catalogue et basculer le groupe
Suppression de VM impossibleSessions actives ou VM statiques attribuéesMode maintenance, vider les sessions, désattribuer
Catalogue en échec à la créationCompte de service sans droits sur l'OU, quotas Azure, image non généraliséeVérifier l'OU, les quotas de la souscription et le journal MCS dans Studio
Divergence entre Studio et le codeModification manuelle dans Web StudioTout changement passe par Terraform ; le plan révèle la dérive
Erreur d'authentificationClient API expiré ou mauvais customer_idRecréer le client sécurisé dans Citrix Cloud, vérifier les variables CITRIX_*

🔷 En contexte Citrix

Chaîne complète : Terraform (azurerm) crée réseau et VM maître → Ansible prépare l'image (ANS-01) → Terraform (citrix) crée catalogue et groupe de mise à disposition.

Commence par une recette : un catalogue de 1 ou 2 VM et un groupe restreint à ton compte, pour valider l'image et l'autoscale avant la production.

Pour les VDA Linux (Ubuntu), le même citrix_machine_catalog s'applique ; seule l'image maître diffère.

À retenir

  • citrix/citrix couvre DaaS et CVAD on-prem ≥ 2311 (avec Web Studio).
  • Zone → connexion → pool → catalogue → groupe : les références ordonnent la création.
  • Secrets en variables d'environnement, jonction AD via citrix_service_account.
  • Mise à jour d'image = changer une variable ; vérifier update in-place dans le plan.
  • Site existant : onboarding helper + import, puis plan sans changement.

Auto-évaluation

Q1. Quelle version minimale de CVAD on-premises le provider supporte-t-il ?

→ 2311, avec Web Studio installé et configuré.

Q2. Que se passe-t-il si restricted_access_users = {} sur un groupe de mise à disposition ?

→ Plus personne n'a accès : listes d'autorisation et de blocage vides. Omis, l'attribut laisse l'accès à tous les utilisateurs authentifiés.

Q3. Comment déployer une nouvelle image sur un catalogue MCS avec Terraform ?

→ Changer la variable de snapshot (master_image), vérifier que le plan propose une mise à jour en place, appliquer, et régler le redémarrage avec image_update_reboot_options.

Q4. Pourquoi éviter de laisser apply_updates_to_existing_machines = true ?

→ Chaque mise à jour du catalogue redémarrerait toutes les VM allumées, sessions comprises.

Pour aller plus loin