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 ActionsParte 1 — Setup local do projeto
Passo 1.1 — Criar repositório local
$REPO = "C:\Users\willi\OneDrive\Documents\Projetos\Data Engineer\de-pipeline-azure"mkdir $REPOSet-Location $REPOgit initPasso 1.2 — Inicializar bundle
databricks bundle init default-pythonVai 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 PrincipalModo 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, exigerun_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.comMover os notebooks
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.pyEdite 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)
# Valida o YAMLdatabricks bundle validate --target dev
# Faz o deploy (envia notebooks + cria/atualiza job no workspace dev)databricks bundle deploy --target dev
# Roda o jobdatabricks bundle run bronze_silver_gold --target devAcompanhe em Workflows no workspace (deve aparecer [dev seuemail] bronze_silver_gold).
Após validar:
databricks bundle destroy --target dev # remove tudoParte 4 — Versionar no GitHub
Passo 4.1 — .gitignore
.gitignore:
.databricks/.databricks-cli/__pycache__/*.pyc.venv/.env*.logPasso 4.2 — Commit + push
Crie o repo no GitHub (privado): gh repo create de-pipeline-azure --private (precisa GitHub CLI) ou pela UI.
git add .git commit -m "feat: initial bundle with bronze→silver→gold pipeline"git branch -M maingit remote add origin https://github.com/SEUUSER/de-pipeline-azure.gitgit push -u origin mainPasso 4.3 — Branches dev e prd
Convenção da trilha:
main= prd.develop= dev.- Feature branches saem de
develop, abrem PR paradevelop. Quando aprovado, PR dedevelopparamainfaz release.
git checkout -b developgit push -u origin developParte 5 — Service Principal para CI/CD (não use seu user em pipelines)
$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 tsvaz role assignment create --assignee $CI_APPID --role Contributor --scope $DBWIDDentro do workspace, dê ao SP permissão de gerar token:
- Admin Settings → Identity and access → Service principals → Add → cole o
appId(display namesp-ci-databricks-deploy). - Adicione à role workspace admin (ou granular: pode criar jobs, clusters).
Parte 6 — Secrets no GitHub
GitHub repo → Settings → Secrets and variables → Actions → New 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 (comhttps://).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 prdEnvironment
production: vá em Settings → Environments → New environment → production. Marque Required reviewers (você mesmo, em projeto solo). Assim, ao fazer merge paramain, o deploy para prd pausa esperando aprovação.
Parte 8 — Testes (pytest)
Crie tests/test_transform.py:
from pyspark.sql import SparkSessionimport 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() == 1Adicione 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-tip2. 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:
- ADF Studio → Manage → Git configuration → conecta no seu repo.
- ADF passa a ler/gravar JSONs em
<repo>/adf-collaboration-branch. - 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
databricks bundle deployfalha com “Cannot resolve variable” → você esqueceu de declarar avariablenodatabricks.ymlou de passar--target.- Job em dev fica com prefixo
[dev ...]no nome — é o mododevelopment. Em prd, nome limpo. run_asem prod: se omitir, deploy roda como o user que disparou. Em prd, sempre use SP.databricks bundle destroyapaga jobs criados pelo bundle. Não apaga tabelas/dados — esses ficam.- 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_SECRETpara o adapter Azure AD. - 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└── .gitignoreChecklist de saída
- Bundle inicializado com
databricks bundle init. -
databricks.ymldefine targetsdeveprdcom variáveis. -
databricks bundle deploy --target devcria/atualiza o job. - Repositório no GitHub.
- Secrets do ARM (tenant/client/secret) cadastrados.
- GitHub Action roda em push para
develope faz deploy automático. - PR para
mainrequer aprovação antes de deploy prd. - Pelo menos 1 teste pytest passa no CI.