ALM

2. Paramétrage d’un workspace Databricks par Terraform

Oct 24, 2022

Nicolas Bailly

Cet article est le 2ème d’une série de 3 articles sur l’automatisation des déploiements Databricks :

  1. Déploiement d’un Workspace Databricks dans Azure avec Terraform
  2. Paramétrage d’un workspace Databricks par Terraform
  3. Pipelines de déploiement d’un environnement Databricks (Infra, paramétrage, notebooks,…)


Databricks nous met à disposition un provider Terraform avec lequel nous pouvons déployer des clusters, des Sql Warehouses, des pipelines, … Nous allons voir dans cet article comment utiliser ce provider avec quelques exemples de workload. Vous trouverez l’ensemble du code source ici.

Initialisation des scripts Terraform

Il est préférable de séparer les scripts gérant l’infrastructure Azure (Article 1) des scripts gérant le paramétrage du workspace.
Si on enchaine tout d’un coup lors de la création de l’environnement (Infra Azure + scripts de paramétrage Databricks), on risque d’avoir des erreurs car la création du workspace prend du temps et peut ne pas être complètement terminée lorsque le paramétrage va commencer à se faire.

Commençons tout d’abord par initialiser le provider Databricks :

data "azurerm_databricks_workspace" "this" {
  name                = "dbw-blog-dev-01"
  resource_group_name = "rg-blog-dev-01"
}

provider "databricks" {
  host = data.azurerm_databricks_workspace.this.workspace_url
}

Dans cet exemple, nous faisons référence au workspace créé dans le précédent article. On s’aperçoit alors que nos scripts ne peuvent paramétrer qu’un seul workspace.

Secret Scope

Il est possible de créer un secret scope Databricks qui pointe vers un Key Vault. C’est intéressant pour variabiliser les client id et les secrets des SPN à utiliser mais aussi pour variabiliser tout ce qui serait nécessaire dans des scripts :

data "azurerm_key_vault" "key_vault_databricks" {
  name                = "kv-blog-dcube-dev-01"
  resource_group_name = "rg-blog-dev-01"
}

resource "databricks_secret_scope" "databricks_secret_scope_key_vault" {
  name                     = "key-vault-secret"
  initial_manage_principal = "users"

  keyvault_metadata {
    resource_id = data.azurerm_key_vault.key_vault_databricks.id
    dns_name    = data.azurerm_key_vault.key_vault_databricks.vault_uri
  }
}

Dans cet exemple, nous faisons référence au Key Vault créé et nous ajoutons un secret scope qui s’y rattache. Vous verrez alors des droits sur le Key Vault pour l’identité « AzureDatabriks ».

L’ajout d’un secret scope ne peut se faire que par un utilisateur et non un SPN. Vous ne pouvez donc pas mettre ceci dans vos scripts si le déploiement se fait par CI/CD.

Création d’un SQL Warehouse

Les Sql Warehouse font partie des workloads Databricks qui permettent d’exécuter des scripts sur du compute. Dans Terraform, c’est appelé « Sql Enpoint ». Voici un exemple d’utilisation :

resource "databricks_sql_endpoint" "this" {
  name                      = "Sql Warehouse example"
  cluster_size              = "2X-Small"
  enable_serverless_compute = true

  tags {
    custom_tags {
      key   = "Application"
      value = "ArticleBlog"
    }
  }
}

resource "databricks_sql_global_config" "this" {
  security_policy = "DATA_ACCESS_CONTROL"
  data_access_config = {
    "spark.hadoop.fs.azure.account.auth.type" : "OAuth",
    "spark.hadoop.fs.azure.account.oauth.provider.type" : "org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider",
    "spark.hadoop.fs.azure.account.oauth2.client.id" : "{{secrets/key-vault-secret/spn-id}}",
    "spark.hadoop.fs.azure.account.oauth2.client.secret" : "{{secrets/key-vault-secret/spn-secret}}",
    "spark.hadoop.fs.azure.account.oauth2.client.endpoint" : "https://login.microsoftonline.com/${data.azurerm_client_config.current.tenant_id}/oauth2/token"
  }
  sql_config_params = {
    "ANSI_MODE" : "true"
  }
}

Ici, nous créons un SQL Warehouse serverless qui utilise un SPN pour s’identifier. Cette identification est importante pour donner des permissions sur le Data Lake à ce SPN. On voit qu’on fait appel au secret scope « key-vault-secret » précédemment créé. Cette syntaxe permet d’aller chercher dans le Key Vault les secrets « spn-id » et « spn-secret ».

À l’heure où cet article est écrit, l’option serverless est en preview et se fait sur demande aux équipes Databricks.

Création d’un pipeline Delta Live Table (DLT)

Delta Live Table permet d’automatiser l’exécution de scripts de type ETL. Vous pouvez les paramétrer par l’interface graphique et les faire pointer vers un notebook. Mais l’utilisation des scripts Terraform rend plus industriel les déploiements et nous assure ainsi d’avoir des environnements identiques.
La déclaration d’un pipeline DLT dans Terraform ressemble beaucoup au JSON que l’on retrouve dans l’interface web de Databricks. Ainsi, vous pouvez mettre en place les pipelines depuis l’interface graphique et récupérer le JSON pour reproduire le paramétrage dans Terraform, comme ceci :

Et voici à quoi cela ressemble une fois en Terraform :

resource "databricks_pipeline" "plague-tale-2-update-gold-data" {
  name    = "pipeline DLT de test"
  storage = "dbfs:/pipelines/pipeline-dlt-de-test"
  target  = "GOLD"

  cluster {
    label               = "default"
    num_workers         = 1
    node_type_id        = "Standard_DS3_v2"
    spark_conf = {
      "spark.databricks.cluster.profile" : "singleNode"
      "fs.azure.account.oauth2.client.id" : "{{secrets/key-vault-secret/spn-id}}"
      "fs.azure.account.oauth.provider.type" : "org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider"
      "spark.databricks.unityCatalog.userIsolation.python.preview" : "true"
      "spark.master" : "local[*, 4]"
      "fs.azure.account.oauth2.client.endpoint" : "https://login.microsoftonline.com/${data.azurerm_client_config.current.tenant_id}/oauth2/token"
      "spark.databricks.delta.preview.enabled" : true
      "fs.azure.account.auth.type" : "OAuth"
      "fs.azure.account.oauth2.client.secret" : "{{secrets/key-vault-secret/spn-secret}}"
    }
  }

  library {
    notebook {
      path = "/shared/Notebooks/pipeline-dlt-de-test"
    }
  }

  continuous  = false
  development = false
  channel     = "current"
  photon      = false
  edition     = "advanced"
}

Vous pouvez voir qu’il est assez simple de reproduire la configuration avec Terraform. Les Data Engineer peuvent se créer leurs pipelines sur un environnement de développement puis reproduire la configuration dans Terraform pour déployer sur les autres environnements.

Création d’un job

Pour finir, nous allons maintenant voir un dernier exemple de ce que l’on peut faire avec le provider Databricks de Terraform.
Nous allons créer un job qui va se lancer en exécutant un script shell d’initialisation pour installer les dépendances puis il va exécuter un notebook. De la même manière que pour les pipelines, le plus simple est de commencer par créer le job dans l’interface graphique et de récupérer le JSON pour ensuite faire le script Terraform.

Dans cet exemple, on a une configuration Spark pour accéder au Data lake avec un SPN, pour le reste, c’est très basic.
Et voici comment transformer ça en Terraform :

resource "databricks_job" "this" {

  name = "Mon-Job-De-Test"

  new_cluster {
    num_workers        = 0
    spark_version      = "11.2.x-scala2.12"
    node_type_id       = "Standard_DS3_v2"
    data_security_mode = "SINGLE_USER"

    spark_conf = {
      "spark.databricks.cluster.profile" : "singleNode"
      "fs.azure.account.oauth2.client.id" : "{{secrets/key-vault-secret/spn-id}}"
      "fs.azure.account.oauth.provider.type" : "org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider"
      "spark.master" : "local[*, 4]"
      "fs.azure.account.oauth2.client.endpoint" : "https://login.microsoftonline.com/${data.azurerm_client_config.current.tenant_id}/oauth2/token"
      "spark.databricks.delta.preview.enabled" : true
      "fs.azure.account.auth.type" : "OAuth"
      "fs.azure.account.oauth2.client.secret" : "{{secrets/key-vault-secret/spn-secret}}"
    }

    custom_tags = {
      "ResourceClass" = "SingleNode"
    }

    init_scripts {
      dbfs {
          destination = "dbfs:/FileStore/init-scripts/init.sh"
        }
    }
  }

  notebook_task {
    notebook_path = /Shared/Notebooks/MonNotebookDeTest
  }
}

Pour aller un peu plus loin dans l’automatisation, si on a plusieurs jobs à créer et qu’ils sont sur le même modèle, on peut se baser sur un tableau de variable de cette manière :

variable "databricks_job_list" {
  type = list(object({
    name          = string
    num_workers   = number
    libraries     = list(string)
    init_script   = string
    notebook_path = string
  }))
}

Ainsi, on peut boucler pour créer autant de jobs que l’on aura d’éléments de la variable :

resource "databricks_job" "this" {
  count = length(var.databricks_job_list)

  name = var.databricks_job_list[count.index].name

  dynamic "library" {
    for_each = toset(var.databricks_job_list[count.index].libraries)
    content {
      pypi {
        package = library.value
      }
    }
  }

  new_cluster {
    num_workers        = var.databricks_job_list[count.index].num_workers
    spark_version      = var.databricks_cluster_version
    node_type_id       = var.databricks_job_cluster_worker_type
    data_security_mode = "SINGLE_USER"

    spark_conf = {
      "spark.databricks.cluster.profile" : "singleNode"
      "fs.azure.account.oauth2.client.id" : "{{secrets/key-vault-secret/spn-id}}"
      "fs.azure.account.oauth.provider.type" : "org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider"
      "spark.master" : "local[*, 4]"
      "fs.azure.account.oauth2.client.endpoint" : "https://login.microsoftonline.com/${data.azurerm_client_config.current.tenant_id}/oauth2/token"
      "spark.databricks.delta.preview.enabled" : true
      "fs.azure.account.auth.type" : "OAuth"
      "fs.azure.account.oauth2.client.secret" : "{{secrets/key-vault-secret/spn-secret}}"
    }

    custom_tags = {
      "ResourceClass" = "SingleNode"
    }

    dynamic "init_scripts" {
      for_each = length(var.databricks_job_list[count.index].init_script) > 0 ? [var.databricks_job_list[count.index].init_script] : []

      content {
        dbfs {
          destination = "dbfs:/FileStore/init-scripts/${init_scripts.value}"
        }
      }
    }
  }

  notebook_task {
    notebook_path = var.databricks_job_list[count.index].notebook_path
  }
}

On a pu voir quelques exemples mais ce provider Databricks permet de faire encore plus de choses. Si vous désirez automatiser la création de vos environnements, il facilite le travail, plutôt que de passer par les API Rest de Databricks.
Nous allons voir dans l’article suivant comment intégrer tout ceci dans les pipelines de CI/CD.

Liens

Code source : https://github.com/dcube/databricks_template/tree/main/infra/databricks

Terraform :

Databricks :

0 commentaires

Soumettre un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Découvrez nos autres articles

git et la face cachée du Rebase

git et la face cachée du Rebase

"Faire une rebase ? *sight* heu... ok..." Jean-Michel Fullstack - Développeur fébrile Jean-Michel est inquiet. En effet, lorsque nous collaborons à plusieurs sur un projet, quelque soit les technologies utilisées, il est important de garder à l'esprit que notre...

lire plus
Aller au contenu principal