Integração Contínua e Entrega Contínua (CI/CD) para Azure Data Factory
Azure Data Factory
O Azure Data Factory ou simplesmente (ADF) é o serviço ETL (do inglês Extract Transform Load) de nuvem do Azure para integração e transformação de dados sem servidor. Ele oferece uma interface de usuário intuitiva e sem código para criação, monitoramento e gerenciamento tudo em um painel único. Você também pode carregar e transferir pacotes SQL Server Integration Services (SSIS) existentes para o Azure e executá-los com total compatibilidade no ADF. O SSIS Integration Runtime oferece um serviço totalmente gerenciado, para que você não precise se preocupar com o gerenciamento da infraestrutura.
CI/CD
No Azure Data Factory, integração contínua e entrega contínua (CI/CD) significa mover pipelines, datasets e outras entidades do Data Factory de um ambiente, como desenvolvimento, teste e produção, para outro. O Azure Data Factory usa modelos do Azure Resource Manager (modelos ARM) para armazenar a configuração de suas várias entidades do Data Factory, como pipelines, conjuntos de dados e fluxos de dados.
Abaixo segue um exemplo de como o Azure Data factory usa modelos ARM para armazenar a configuração de suas várias entidades:
{
"name": "myPipeline",
"type": "Microsoft.DataFactory/factories/pipelines",
"apiVersion": "2018-06-01",
"properties": {
"activities": [
{
"name": "CopyFromBlobToBlob",
"type": "Copy",
"dependsOn": [],
"policy": {
"timeout": "7.00:00
Fluxos de trabalho de CI/CD
Atualmente existem dois fluxos de trabalho para CI/CD para o Azure Data Factory:
- Um fluxo que possui um processo manual para que os arquivos ARM sejam copiados para um repositório de controle de versão, como o Azure Repos ou GitHub. Em seguida, você pode usar o Azure Pipelines para implantar os arquivos ARM para o Data Factory.

Onde:
- Cada analista faz alterações em suas features branches.
- Fazer o push na Collaboration branch não é permitido. Os analistas devem criar uma solicitação pull para fazer alterações.
- Os usuários devem carregar o Azure Data Factory Studio e selecionar Publish para implantar alterações no Data Factory e gerar os arquivos ARM na Publish branch.
- Um pipeline de Release no Azure DevOps é configurado para criar uma nova versão e implantar os arquivos ARM sempre que uma nova alteração é enviada por push para a Publish branch.
- E um segundo fluxo totalmente automatizado que usa o Azure Pipelines para gerar e validar a criação dos arquivos ARM e em seguida, implantar os arquivos ARM para o Data Factory.

Onde:
- Cada analista faz alterações em suas features branches.
- Fazer o push na Collaboration branch não é permitido. Os analistas devem criar uma solicitação pull para fazer alterações.
- Um pipeline do Azure DevOps é acionado sempre que uma nova alteração é realizada na Collaboration branch. Ele valida os recursos e gera um modelo ARM como um artefato se a validação for bem-sucedida.
- o pipeline de deploy é configurado para criar implantar os arquivos ARM sempre que a etapa anterior form bem-sucedida.
É no segundo fluxo que vamos focar nesse artigo.
Preparando o ambiente
Na criação do pipeline de CI/CD será necessário utilizar o pacote @microsoft/azure-data-factory-utilities que é um pacote de utilitários para o Azure Data Factory. Esse pacote contém um conjunto de ferramentas para ajudar a exportar e validar pipelines do Data Factory. Um arquivo package.json
deve ser criado na raiz do repositório e adicionado a seguinte dependência:
{
"scripts":{
"build":"node node_modules/@microsoft/azure-data-factory-utilities/lib/index"
},
"dependencies":{
"@microsoft/azure-data-factory-utilities":"^0.1.6"
}
}
Uma das tarefas do pipeline será copiar os arquivos ARM para um Storage Account. Há alguns limites para a utilização dos arquivos ARM. O tamanho máximo de um arquivo é de 4MB e quantidade de recursos em um arquivo é 800, para conhecer mais sobre os limites dos arquivos ARM acesse esse link. Para contornar esse problema, os arquivos serão divididos em pacotes de 4MB e armazenados no Storage Account.
Para isso, será necessário criar um Service Principal com permissão de acesso ao Storage Account. Para criar o Service Principal, execute o seguinte comando no Azure CLI:
Caso não tenha o Azure CLI instalado, você pode instalar seguindo as instruções aqui.
az ad sp create-for-rbac --name "api://ADF-SP" --role "Storage Blob Data Contributor" --scopes "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<storage-account>"
O comando acima irá retornar um JSON com as credenciais do Service Principal criado. Anote o appId
, tenantId
, e o password
.
Crie uma service connection no Azure DevOps para o Service Principal criado. Para criar a service connection, acesse o Azure DevOps e acesse o projeto que o ADF foi conectado e clique em Project settings
e em seguida em Service connections
. Clique em New service connection
e selecione Azure Resource Manager
e clique em Next
. Selecione Service principal (manual)
e clique em Next
. Preencha os campos com as informações do Service Principal criado e clique em Verify and save
.
Caso não tenha o Azure Storage Account, acesse esse link e siga as instruções.
Um SAS Token também será necessário para que o processo de CI/CD possa acessar o Storage Account. Para criar o SAS Token, acesse esse link e siga as instruções.
Com o SAS Token em mãos e o Storage Account criado, vamos criar o pipeline de CI/CD.
Criando o pipeline de CI/CD
Para criar o pipeline de CI/CD, acesse o Azure DevOps e acesse o projeto que o ADF foi conectado e clique em Pipelines
e em seguida em New pipeline
. Certifique-se de que o repositório correto está selecionado e clique em Starter pipeline
.

Substitua o conteúdo do arquivo azure-pipelines.yml
pelo seguinte:
trigger:
- main
variables:
- group: adf
- name: subscriptionId
value: <Subscription Id>
- name: serviceConnection
value: <Service Connection Id>
- name: dataFactoryNameDev
value: <Data Factory Name Dev>
- name: dataFactoryNameHml
value: <Data Factory Name Hml>
- name: datafactoryRgName
value: <Resource Group Name>
- name: dataFactoryID
value: /subscriptions/$(subscriptionId)/resourceGroups/$(datafactoryRgName)/providers/Microsoft.DataFactory/factories/$(dataFactoryNameDev)
- name: blobContainerName
value: armartifacts
- name: storageAccountName
value: <Storage Account Name>
- name: StorageURL
value: https://$(storageAccountName).blob.core.windows.net/$(blobContainerName)/ARMTemplate/linkedTemplates
- name: storageAccountUri
value: https://$(storageAccountName).blob.core.windows.net
- name: location
value: <location>
- name: NewAzResourceLock
value: 'False'
- name: LockLevel
value: '--'
- name: LockNotes
value: '--'
- name: LockName
value: '--'
stages:
- stage: Build
displayName: Build ADF
jobs:
- template: Template/build-adf.yml
parameters:
devDataFactoryID: $(dataFactoryID)
- stage: DeployToDev
displayName: Deploy em Dev
dependsOn: Build
condition: succeeded()
jobs:
- template: Template/deploy-adf.yml
parameters:
datafactoryRgName: $(datafactoryRgName)
resourceManagerConnection: $(serviceConnection)
location: $(location)
subscriptionId: $(subscriptionId)
subscription: $(serviceConnection)
dataFactoryName: $(dataFactoryNameDev)
dataFactoryNameDev: $(dataFactoryNameDev)
storageAccountName: $(storageAccountName)
blobContainerName: $(blobContainerName)
StorageURL: $(StorageURL)
StorageSASToken: $(StorageSASToken)
environment: dev
automatePublish: true
- stage: DeployToHml
displayName: Deploy em Hml
dependsOn: DeployToDev
condition: succeeded()
jobs:
- template: Template/deploy-adf.yml
parameters:
datafactoryRgName: $(datafactoryRgName)
resourceManagerConnection: $(serviceConnection)
location: $(location)
subscriptionId: $(subscriptionId)
subscription: $(serviceConnection)
dataFactoryName: $(dataFactoryNameHml)
dataFactoryNameDev: $(dataFactoryNameDev)
storageAccountName: $(storageAccountName)
blobContainerName: $(blobContainerName)
StorageURL: $(StorageURL)
StorageSASToken: $(StorageSASToken)
environment: hml
Substitua os valores das variáveis <Subscription Id>
, <Service Connection Id>
, <Data Factory Name Dev>
(ambiente de desenvolvimento), <Data Factory Name Hml>
(ambiente de homologação), <location>
(ex. brazilsouth, eastus, …), <Resource Group Name>
e <Storage Account Name>
pelos valores correspondentes.
Salve o arquivo, mas não execute o pipeline ainda.
O pipeline de CI/CD criado acima possui 3 stages:
- Build: Responsável por executar o processo de build do ADF. O stage Build possui um job que executa o template
build-adf.yml
, onde o processo de exportação e validação do ADF é executado. Após a execução os arquivos ARM são armazenados no Azure Pipelines e o job é finalizado. - DeployToDev: Responsável por executar o processo de deploy do ADF em Dev. O stage deployToDev possui um job que executa o template
deploy-adf.yml
, onde o processo de deploy do ADF atualiza o ambiente de Dev com as alterações realizadas. Após a execução o job é finalizado. - DeployToHml: Responsável por executar o processo de deploy do ADF em Hml. O stage deployToHml possui um job que executa o template
deploy-adf.yml
, onde o processo de deploy do ADF atualiza o ambiente de Hml com as alterações realizadas. Após a execução o job é finalizado.
No repositório crie duas pastas chamadas Template
e Scripts
.
Navegue a até a pasta Template
e crie um novo arquivo chamado build-adf.yml
. Substitua o conteúdo do arquivo pelo seguinte:
parameters:
- name: devDataFactoryID
jobs:
- job: BuildADFDev
displayName: Build Adf Dev
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '16.x'
displayName: 'Install Node.js'
- task: Npm@1
inputs:
command: 'install'
workingDir: '$(Build.Repository.LocalPath)'
verbose: true
displayName: 'Install npm package'
- task: Npm@1
inputs:
command: 'custom'
workingDir: '$(Build.Repository.LocalPath)'
customCommand: 'run build validate $(Build.Repository.LocalPath) ${{ parameters.devDataFactoryID }}'
displayName: 'Validate'
- task: Npm@1
inputs:
command: 'custom'
workingDir: '$(Build.Repository.LocalPath)'
customCommand: 'run build export $(Build.Repository.LocalPath) ${{ parameters.devDataFactoryID }} "ArmTemplate"'
displayName: 'Generate ARM template'
- task: CopyFiles@2
displayName: Copiando os arquivos do IaC
inputs:
SourceFolder: '$(Build.Repository.LocalPath)'
Contents: |
ArmTemplate/**/*
Scripts/**/*
TargetFolder: '$(Build.ArtifactStagingDirectory)/dropIAC'
- task: PublishPipelineArtifact@1
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/dropIAC'
artifact: 'dropIAC'
publishLocation: 'pipeline'
Na sequência, crie um novo arquivo chamado deploy-adf.yml
e substitua o conteúdo do arquivo pelo seguinte:
parameters:
- name: datafactoryRgName
- name: resourceManagerConnection
- name: location
- name: subscriptionId
- name: subscription
- name: dataFactoryNameDev
- name: dataFactoryName
- name: storageAccountName
- name: blobContainerName
- name: StorageURL
- name: StorageSASToken
- name: environment
- name: automatePublish
type: boolean
default: false
jobs:
- deployment: Deploy
displayName: Deploy do Data Factory
pool:
vmImage: 'windows-latest'
environment: ${{ parameters.environment }}
strategy:
runOnce:
deploy:
steps:
- task: DownloadPipelineArtifact@2
displayName: Download do artefato ArmTemplate
inputs:
buildType: 'current'
artifactName: 'dropIAC'
targetPath: '$(Build.ArtifactStagingDirectory)'
- task: AzureFileCopy@4
displayName: 'Copy ARMTemplates to Storage Account'
inputs:
SourcePath: '$(Build.ArtifactStagingDirectory)/ARMTemplate'
azureSubscription: ${{ parameters.subscription }}
Destination: AzureBlob
storage: ${{ parameters.storageAccountName }}
ContainerName: '${{ parameters.blobContainerName }}'
- task: AzurePowerShell@5
displayName: 'Script Pré-Deployment'
inputs:
azureSubscription: ${{ parameters.subscription }}
ScriptType: FilePath
ScriptPath: $(Build.ArtifactStagingDirectory)/Scripts/Pre-Pos-Deployment.ps1
ScriptArguments: -armTemplate $(Build.ArtifactStagingDirectory)/ARMTemplate/ARMTemplateForFactory.json -ResourceGroupName ${{ parameters.datafactoryRgName }} -DataFactoryName ${{ parameters.dataFactoryName }} -predeployment $true -deleteDeployment $false
azurePowerShellVersion: LatestVersion
workingDirectory: $(Build.ArtifactStagingDirectory)
pwsh: true
- task: AzurePowerShell@5
displayName: 'Remove Resource Group Lock'
inputs:
azureSubscription: ${{ parameters.subscription }}
ScriptType: 'FilePath'
ScriptPath: '$(Build.ArtifactStagingDirectory)/Scripts/verifyResourceLock.ps1'
ScriptArguments: '-removeLock "True" -ResourceGroupName ${{ parameters.datafactoryRgName }}'
azurePowerShellVersion: 'LatestVersion'
pwsh: true
- task: AzureResourceManagerTemplateDeployment@3
displayName: ARM Template - Deploy do ADF ${{ parameters.dataFactoryName }}
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: ${{ parameters.resourceManagerConnection }}
subscriptionId: ${{ parameters.subscriptionId }}
action: 'Create Or Update Resource Group'
resourceGroupName: ${{ parameters.datafactoryRgName }}
location: ${{ parameters.location }}
templateLocation: 'Linked artifact'
csmFile: '$(Build.ArtifactStagingDirectory)/ARMTemplate/linkedTemplates/ArmTemplate_master.json'
csmParametersFile: '$(Build.ArtifactStagingDirectory)/ARMTemplate/linkedTemplates/ArmTemplateParameters_master.json'
overrideParameters: >-
-factoryName ${{ parameters.dataFactoryName }} -containerUri ${{ parameters.StorageURL }} -containerSasToken ${{ parameters.StorageSASToken }}
deploymentMode: 'Incremental'
- task: AzurePowerShell@5
displayName: 'Script Pos-Deployment'
inputs:
azureSubscription: ${{ parameters.subscription }}
ScriptType: 'FilePath'
ScriptPath: '$(Build.ArtifactStagingDirectory)/Scripts/Pre-Pos-Deployment.ps1'
ScriptArguments: '-armTemplate $(Build.ArtifactStagingDirectory)/ARMTemplate/ARMTemplateForFactory.json -ResourceGroupName ${{ parameters.datafactoryRgName }} -DataFactoryName ${{ parameters.dataFactoryName }} -predeployment $false -deleteDeployment $true'
azurePowerShellVersion: 'LatestVersion'
pwsh: true
- ${{ if eq(parameters.automatePublish, 'true') }}:
- task: AzurePowerShell@5
displayName: 'Automate Publish'
inputs:
azureSubscription: ${{ parameters.subscription }}
ScriptType: 'FilePath'
ScriptPath: '$(Build.ArtifactStagingDirectory)/Scripts/UpdateLastCommitId.ps1'
ScriptArguments: '-ResourceGroupName ${{ parameters.datafactoryRgName }} -DataFactoryName ${{ parameters.dataFactoryName }} -LastCommitId $(Build.SourceVersion)'
azurePowerShellVersion: 'LatestVersion'
pwsh: true
- task: AzurePowerShell@5
displayName: 'Create Resource Group Lock'
inputs:
azureSubscription: ${{ parameters.subscription }}
ScriptType: 'FilePath'
ScriptPath: '$(Build.ArtifactStagingDirectory)/Scripts/verifyResourceLock.ps1'
ScriptArguments: '-newLock $(NewAzResourceLock) -ResourceGroupName ${{ parameters.datafactoryRgName }} -LockLevel $(LockLevel) -LockNotes $(LockNotes) -LockName $(LockName)'
azurePowerShellVersion: 'LatestVersion'
pwsh: true
O template deploy-adf.yml
utiliza alguns scripts que estão na pasta Scripts
do repositório, eles são necessários para que o processo de deploy funcione corretamente. Cada script tem uma função específica, abaixo estão as descrições e conteúdo de cada script.
Script para parar a execução das triggers do Data Factory O script Pre-Pos-Deployment.ps1
é responsável por parar a execução das triggers do Data Factory. Esse script é utilizado para que a publicação do Data Factory não apresente erro por estar em execução no momento do deploy, esse script também é responsável por reativar as trigger do Data Factory após o deploy e por deletar o deployment do Data Factory após o deploy. para ter acesso ao script acesse esse repositório no Github Azure/Azure-DataFactory.
Script para atualizar o commit id do Data Factory O script UpdateLastCommitId.ps1
é responsável por atualizar o commit id do Data Factory. Esse script é utilizado para que o Data Factory seja publicado automaticamente após o deploy.
param(
[parameter(Mandatory = $true)] [String]$ResourceGroupName,
[parameter(Mandatory = $true)] [String]$DataFactoryName,
[parameter(Mandatory = $true)] [String]$LastCommitId
)
$var = Get-AzDataFactoryV2 -ResourceGroupName $ResourceGroupName -Name $DataFactoryName
Get-AzDataFactoryV2 -ResourceGroupName $ResourceGroupName -Name $DataFactoryName | Set-AzDataFactoryV2 -AccountName $var.RepoConfiguration.AccountName -RepositoryName $var.RepoConfiguration.RepositoryName -CollaborationBranch $var.RepoConfiguration.CollaborationBranch -RootFolder / -ProjectName $var.RepoConfiguration.ProjectName -LastCommitId $LastCommitId -Force
Script para remover o lock do Resource Group O script verifyResourceLock.ps1
é responsável por remover o lock do Resource Group e criar um novo lock com as informações passadas como parâmetro.
param (
[parameter(Mandatory = $false)] [System.String] $removeLock = "False",
[parameter(Mandatory = $false)] [System.String] $newLock = "False",
[parameter(Mandatory = $false)] [System.String] $ResourceGroupName = "rg-name",
[parameter(Mandatory = $false)] [System.String] $LockLevel = "CanNotDelete",#CanNotDelete / ReadOnly
[parameter(Mandatory = $false)] [System.String] $LockNotes = "Azure-DevOps-Pipeline",
[parameter(Mandatory = $false)] [System.String] $LockName = "ProductionLocked"
)
function RemoveAzResourceLock {
param(
[System.String] $ResourceGroupName
)
$AzResourceLock = Get-AzResourceLock -ResourceGroupName $ResourceGroupName -AtScope
# verify if exists an resource lock
if (!$AzResourceLock) {
Write-Host "There is no Resource Lock for Resource Group: " $ResourceGroupName
$return = $false
$return = $return | Select-Object @{Name = 'Removed';Expression = {$_}}
$NewAzResourceLock = "False"
Write-Host "##vso[task.setvariable variable=NewAzResourceLock;]$NewAzResourceLock"
return $return
} else {
$RemoveAzResourceLock = Remove-AzResourceLock -ResourceGroupName $AzResourceLock.ResourceGroupName -LockName $AzResourceLock.Name -Force
Write-Host "Resource Lock Removed? " $RemoveAzResourceLock
$return = $AzResourceLock | Select-Object Name, @{Name = 'Level';Expression = {"$($_.Properties.level)"}}, @{Name = 'Notes';Expression = {"$($_.Properties.notes)"}}, @{Name = 'Removed';Expression = {"True"}}
$ResourceGroupName = $AzResourceLock.ResourceGroupName
$NewAzResourceLock = "True"
$LockLevel = $AzResourceLock.Properties.level
$LockNotes = $AzResourceLock.Properties.notes ? $AzResourceLock.Properties.notes : "Azure-DevOps-Pipeline"
$LockName = $AzResourceLock.Name
Write-Host "##vso[task.setvariable variable=NewAzResourceLock;]$NewAzResourceLock"
Write-Host "##vso[task.setvariable variable=ResourceGroupName;]$ResourceGroupName"
Write-Host "##vso[task.setvariable variable=LockLevel;]$LockLevel"
Write-Host "##vso[task.setvariable variable=LockNotes;]$LockNotes"
Write-Host "##vso[task.setvariable variable=LockName;]$LockName"
return $return
}
}
function NewAzResourceLock {
param(
[System.String] $NewAzResourceLock,
[System.String] $ResourceGroupName,
[System.String] $LockLevel,
[System.String] $LockNotes,
[System.String] $LockName
)
if ($NewAzResourceLock -eq 'True'){
$ResourceLock = New-AzResourceLock -ResourceGroupName $ResourceGroupName -LockLevel $LockLevel -LockNotes $LockNotes -LockName $LockName -Force
Write-Host "Resource Lock Created: " $ResourceLock.ResourceId
} else {
Write-Host "There is no Resource Lock for Resource Group: " $ResourceGroupName
}
}
if ($removeLock -eq 'True') {
Write-Host "Remove lock"
RemoveAzResourceLock -ResourceGroupName $ResourceGroupName
}
if ($newLock -eq 'True') {
Write-Host "Create lock"
NewAzResourceLock -NewAzResourceLock $newLock -ResourceGroupName $ResourceGroupName -LockLevel $LockLevel -LockNotes $LockNotes -LockName $LockName
}
Agora que já todos os scripts estão criados é necessário criar um variable group no Azure DevOps para armazenar o SAS Token criado anteriormente.
Para criar o variable group no Azure DevOps basta clicar em Library
> Variable Groups
> New Variable Group
, preencha o nome do variable group com adf
e clique em Add Variable
. Adicione a variável abaixo:
StorageSASToken | ? + Token gerado anteriormente*

nota: Não esqueça de clicar no cadeado para ocultar o Token.
Executando o Pipeline
Após todos os passos anteriores, o pipeline de CI/CD está pronto para ser executado. Clique em Pipelines
> selecione o pipeline criado > Run pipeline
.
Um exemplo de execução com sucesso do pipeline pode ser visto abaixo:




Conclusão
A utilização do Azure DevOps para o CI/CD de Data Factory é uma ótima opção para quem deseja automatizar o processo de deploy de Data Factory. O pipeline criado neste artigo é um exemplo , mas existem outras formas de automação, como por exemplo, utilizando o Azure Data Factory V2 REST API.
Referências
- Azure Data Factory
- Azure DevOps
- Azure DevOps Variable Groups
- Azure DevOps Pipeline
- Automatizar a integração contínua usando versões do Azure Pipelines
- Integração e entrega contínuas no Azure Data Factory
- Controle do código-fonte no Azure Data Factory
- Linked Resource Manager templates with CI/CD
- Usar parâmetros personalizados com o modelo do Resource Manager
- CLI Az.DataFactory
- Melhorias de implantação contínua
- Script para o Pré e Pós Deployment Versão II
- Início Rápido: criar um Azure Data Factory usando o Bicep
- Criar tokens SAS para os contêineres de armazenamento
Updated Mar 06, 2023
Version 1.0jailtons
Microsoft
Joined November 08, 2021
Desenvolvedores BR
Follow this blog board to get notified when there's new activity