DE Trilhas

M07 — CI/CD com Databricks Asset Bundles + GitHub Actions

Duração: 3h Pré-requisitos: M00-M06 ok. Conta GitHub. Objetivo: versionar notebooks + jobs + pipelines num único projeto Git, fazer deploy para dev automático e para prd via Pull Request, usando Databricks Asset Bundles (DAB) e GitHub Actions.

Por que DAB

Antes do DAB:

  • Notebooks viviam só no workspace, desversionados.
  • Quem usava CI fazia gambiarra com databricks workspace import + databricks jobs create/update.
  • Diferenças entre dev/hml/prd eram manuais.

DAB = arquivo YAML que descreve TUDO:

  • Workspaces (dev, hml, prd) → variáveis por ambiente.
  • Recursos (jobs, pipelines DLT, ml endpoints) → criados/atualizados via 1 comando.
  • Notebooks/wheels → enviados juntos.
+ Estrutura padrão DAB:
+
+ databricks.yml ← manifesto principal
+ resources/
+ job-bronze-silver.yml ← define o job (cluster, notebook, schedule)
+ notebooks/
+ 01_bronze_to_silver.py
+ 02_silver_to_gold.py
+ src/
+ common.py ← módulos Python compartilhados
+ tests/
+ test_common.py
+ pyproject.toml
+ .github/workflows/
+ deploy.yml ← GitHub Actions

Parte 1 — Setup local do projeto

Passo 1.1 — Criar repositório local

Terminal window
$REPO = "C:\Users\willi\OneDrive\Documents\Projetos\Data Engineer\de-pipeline-azure"
mkdir $REPO
Set-Location $REPO
git init

Passo 1.2 — Inicializar bundle

Terminal window
databricks bundle init default-python

Vai perguntar:

  • Project name: de_pipeline_azure.
  • Include sample DLT pipeline? no.
  • Include sample notebook? yes.

Será criado:

de-pipeline-azure/
├── databricks.yml
├── resources/
│ └── de_pipeline_azure.job.yml
├── src/
├── tests/
├── README.md
└── ...

Passo 1.3 — Configurar 2 ambientes (dev e prd)

Edite databricks.yml:

bundle:
name: de_pipeline_azure
include:
- resources/*.yml
variables:
storage_account:
description: nome do storage por ambiente
catalog:
description: catalog UC por ambiente
targets:
dev:
mode: development
default: true
workspace:
host: https://adb-XXXXXXXX.XX.azuredatabricks.net # seu workspace dev
variables:
storage_account: stdewdevbrs
catalog: silver_dev
prd:
mode: production
workspace:
host: https://adb-YYYYYYYY.YY.azuredatabricks.net # workspace prd (se ainda não tem, simule com o mesmo)
root_path: /Shared/.bundle/${bundle.name}/prd
variables:
storage_account: stdewprdbrs
catalog: silver_prd
run_as:
user_name: "deploy-sp@seudominio.com" # opcional; em prd use Service Principal

Modo development vs production:

  • development: prefixa o nome dos jobs com seu user ([dev willamarques] job-x), permite triggers desativados por padrão. Bom pra sandbox.
  • production: nomes limpos, exige run_as (não roda como pessoa física), valida com mais rigor.

Parte 2 — Definir o job em YAML

Substitua resources/de_pipeline_azure.job.yml:

resources:
jobs:
bronze_silver_gold:
name: bronze_silver_gold
tasks:
- task_key: bronze_to_silver
notebook_task:
notebook_path: ../notebooks/02_bronze_to_silver.py
base_parameters:
storage_account: ${var.storage_account}
catalog: ${var.catalog}
job_cluster_key: small_cluster
- task_key: silver_to_gold
depends_on:
- task_key: bronze_to_silver
notebook_task:
notebook_path: ../notebooks/03_silver_to_gold.py
base_parameters:
catalog: ${var.catalog}
job_cluster_key: small_cluster
job_clusters:
- job_cluster_key: small_cluster
new_cluster:
spark_version: "15.4.x-scala2.12"
node_type_id: Standard_DS3_v2
num_workers: 0
data_security_mode: SINGLE_USER
spark_conf:
spark.databricks.cluster.profile: singleNode
spark.master: local[*, 4]
custom_tags:
projeto: de
ambiente: ${bundle.target}
trilha: azure-e2e
schedule:
quartz_cron_expression: "0 0 9 * * ?" # 06:00 BRT = 09:00 UTC
timezone_id: UTC
pause_status: ${var.bundle_target == "prd" ? "UNPAUSED" : "PAUSED"}
email_notifications:
on_failure:
- team.old.schoolll@gmail.com

Mover os notebooks

Terminal window
mkdir "$REPO\notebooks"
# Copie o conteúdo de 02_bronze_to_silver.py (M04) para notebooks/02_bronze_to_silver.py
# Copie 03_silver_to_gold.py (M06) para notebooks/03_silver_to_gold.py

Edite os notebooks para receber storage_account e catalog via widgets:

dbutils.widgets.text("storage_account", "")
dbutils.widgets.text("catalog", "")
storage = dbutils.widgets.get("storage_account")
catalog = dbutils.widgets.get("catalog")

Parte 3 — Validar, deploy e run local (dev)

Terminal window
# Valida o YAML
databricks bundle validate --target dev
# Faz o deploy (envia notebooks + cria/atualiza job no workspace dev)
databricks bundle deploy --target dev
# Roda o job
databricks bundle run bronze_silver_gold --target dev

Acompanhe em Workflows no workspace (deve aparecer [dev seuemail] bronze_silver_gold).

Após validar:

Terminal window
databricks bundle destroy --target dev # remove tudo

Parte 4 — Versionar no GitHub

Passo 4.1 — .gitignore

.gitignore:

.databricks/
.databricks-cli/
__pycache__/
*.pyc
.venv/
.env
*.log

Passo 4.2 — Commit + push

Crie o repo no GitHub (privado): gh repo create de-pipeline-azure --private (precisa GitHub CLI) ou pela UI.

Terminal window
git add .
git commit -m "feat: initial bundle with bronze→silver→gold pipeline"
git branch -M main
git remote add origin https://github.com/SEUUSER/de-pipeline-azure.git
git push -u origin main

Passo 4.3 — Branches dev e prd

Convenção da trilha:

  • main = prd.
  • develop = dev.
  • Feature branches saem de develop, abrem PR para develop. Quando aprovado, PR de develop para main faz release.
Terminal window
git checkout -b develop
git push -u origin develop

Parte 5 — Service Principal para CI/CD (não use seu user em pipelines)

Terminal window
$SPCI = az ad sp create-for-rbac --name "sp-ci-databricks-deploy" --query "{appId:appId, password:password, tenant:tenant}" -o json | ConvertFrom-Json
$CI_APPID = $SPCI.appId
$CI_SECRET = $SPCI.password
$CI_TENANT = $SPCI.tenant
# Permissão de Contributor no workspace Databricks
$DBWID = az databricks workspace show -n "dbw-de-dev-brs" -g $RG --query id -o tsv
az role assignment create --assignee $CI_APPID --role Contributor --scope $DBWID

Dentro do workspace, dê ao SP permissão de gerar token:

  1. Admin SettingsIdentity and accessService principalsAdd → cole o appId (display name sp-ci-databricks-deploy).
  2. Adicione à role workspace admin (ou granular: pode criar jobs, clusters).

Parte 6 — Secrets no GitHub

GitHub repo → SettingsSecrets and variablesActionsNew repository secret.

Crie:

  • ARM_TENANT_ID = $CI_TENANT.
  • ARM_CLIENT_ID = $CI_APPID.
  • ARM_CLIENT_SECRET = $CI_SECRET.
  • DATABRICKS_HOST_DEV = URL do workspace dev (com https://).
  • DATABRICKS_HOST_PRD = URL prd (ou mesma do dev por enquanto).

Mais seguro: GitHub OIDC + Federated Identity (sem secret estático). Aprenda isto quando dominar o básico — mas em entrevista cite que sabe que existe.

Parte 7 — GitHub Actions

Crie .github/workflows/deploy.yml:

name: deploy-bundle
on:
push:
branches: [develop, main]
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: databricks/setup-cli@main
- name: validate bundle (dev)
env:
DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST_DEV }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
run: databricks bundle validate --target dev
deploy-dev:
if: github.ref == 'refs/heads/develop'
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: databricks/setup-cli@main
- name: deploy dev
env:
DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST_DEV }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
run: |
databricks bundle deploy --target dev
databricks bundle run bronze_silver_gold --target dev || echo "smoke run failed (non-blocking)"
deploy-prd:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: validate
runs-on: ubuntu-latest
environment: production # requer aprovação manual (configurado em Settings → Environments)
steps:
- uses: actions/checkout@v4
- uses: databricks/setup-cli@main
- name: deploy prd
env:
DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST_PRD }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
run: databricks bundle deploy --target prd

Environment production: vá em Settings → Environments → New environment → production. Marque Required reviewers (você mesmo, em projeto solo). Assim, ao fazer merge para main, o deploy para prd pausa esperando aprovação.

Parte 8 — Testes (pytest)

Crie tests/test_transform.py:

from pyspark.sql import SparkSession
import pytest
@pytest.fixture(scope="session")
def spark():
return (SparkSession.builder
.appName("tests")
.master("local[2]")
.getOrCreate())
def test_dedup_remove_duplicatas(spark):
df = spark.createDataFrame(
[(1, "A"), (1, "A"), (2, "B")],
["id", "v"]
)
out = df.dropDuplicates(["id"])
assert out.count() == 2
def test_filtro_outliers(spark):
df = spark.createDataFrame([(1.0,), (1000.0,), (0.05,)], ["trip_distance_km"])
out = df.filter("trip_distance_km BETWEEN 0.1 AND 200")
assert out.count() == 1

Adicione job test no .github/workflows/deploy.yml (antes do validate):

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.11' }
- run: |
pip install pyspark==3.5.* pytest delta-spark==3.2.*
pytest -q tests/

E faça validate: needs: test.

Parte 9 — Promoção dev → prd: o fluxo

1. Crie feature branch: git checkout -b feat/nova-coluna-tip
2. Faça alteração no notebook.
3. Commit, push, PR para develop.
4. GH Actions roda test + validate.
5. Merge → deploy dev automático.
6. Valide em dev (rodar job, checar tabela).
7. PR de develop → main.
8. Merge → GH Actions roda test + validate.
9. Aguarda aprovação (Environment production).
10. Aprovou → deploy prd.

Parte 10 — Versionar também o ADF (preview)

ADF tem integração nativa Git:

  1. ADF Studio → ManageGit configuration → conecta no seu repo.
  2. ADF passa a ler/gravar JSONs em <repo>/adf-collaboration-branch.
  3. Toda alteração no Studio vira commit. Você abre PR como em qualquer projeto.

JSONs ficam em pastas como pipeline/, dataset/, linkedService/, trigger/. Você pode adicionar workflows GitHub Actions específicos do ADF (com az datafactory pipeline create).

Esta parte é só pra você saber. Implementação completa pode ficar pra um M07b futuro.

Pegadinhas

  1. databricks bundle deploy falha com “Cannot resolve variable” → você esqueceu de declarar a variable no databricks.yml ou de passar --target.
  2. Job em dev fica com prefixo [dev ...] no nome — é o modo development. Em prd, nome limpo.
  3. run_as em prod: se omitir, deploy roda como o user que disparou. Em prd, sempre use SP.
  4. databricks bundle destroy apaga jobs criados pelo bundle. Não apaga tabelas/dados — esses ficam.
  5. GitHub Actions falha autenticação → você confundiu Tenant ID / Client ID / Object ID. Os secrets corretos são ARM_TENANT_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET para o adapter Azure AD.
  6. Bundle “mode: production” exige paths absolutos começando com /Shared/. Se usar /Users/..., falha.

Estrutura final do repositório

de-pipeline-azure/
├── .github/
│ └── workflows/
│ └── deploy.yml
├── notebooks/
│ ├── 02_bronze_to_silver.py
│ └── 03_silver_to_gold.py
├── resources/
│ └── de_pipeline_azure.job.yml
├── src/
│ └── common/
│ └── transforms.py
├── tests/
│ └── test_transform.py
├── databricks.yml
├── pyproject.toml
├── README.md
└── .gitignore

Checklist de saída

  • Bundle inicializado com databricks bundle init.
  • databricks.yml define targets dev e prd com variáveis.
  • databricks bundle deploy --target dev cria/atualiza o job.
  • Repositório no GitHub.
  • Secrets do ARM (tenant/client/secret) cadastrados.
  • GitHub Action roda em push para develop e faz deploy automático.
  • PR para main requer aprovação antes de deploy prd.
  • Pelo menos 1 teste pytest passa no CI.

Próximo

M08 — Pipeline fim a fim — case completo

← Anterior
M06 Unity Catalog Governanca
Próximo →
M08 Pipeline Fim A Fim