Atualize variáveis do Azure DevOps com Terraform

Quando você provisiona recursos de nuvem com o Terraform, é comum que eles gerem dados secretos e/ou aleatórios (como um ID, ou uma string de conexão) que você pode precisar usar posteriormente ao implantar sua aplicação. Mas normalmente a infraestrutura e a aplicação são provisionados em momentos separados - inclusive em pipelines separados - então como a gente pode pegar essas dados de infra e passar para o pipeline da aplicação?

Ao invés de digitar esses dados manualmente, veja como você pode atualizar variáveis no Azure DevOps usando o próprio Terraform.

Imagine uma situação ao mesmo tempo simples e típica: você cria uma aplicação em ASP.NET Core e quer hospedá-la no Azure. Para isso, você vai usar o Terraform para provisionar os recursos necessários, como um App Service, um banco de dados SQL e um Application Insights.

Quando o Terraform criar seus recursos, vão ser gerados dados como a string de conexão para o banco de dados e a chave de instrumentação para o Application Insights. Você vai precisar desses dados no momento em que fizer a implantação da aplicação, certo?

Tipicamente, essas informações são mantidas no Azure DevOps na forma de variáveis de pipeline, que são aplicadas no instante da publicação. Mas como esses dados podem mudar a cada novo deployment do seu ambiente, você precisaria ir manualmente no portal do Azure para obter esses dados e atualizar o valor das variáveis.

Será mesmo que não tem jeito melhor de fazer isso? Como atualizar as variáveis do Azure DevOps com esses dados gerados dinamicamente pelo Terraform?

Atualizando o Azure DevOps via Terraform

O Terraform é uma ferramenta de infraestrutura como código (IaC) que permite que você provisione recursos de nuvem de forma declarativa. Para isso, ele usa “provedores” (“providers”), que são componentes especializados que permitem que você interaja com diferentes serviços de nuvem. Por exemplo, o provider azurerm é usado para criar os diversos recursos do Azure via Terraform.

Agora, o Terraform tem também um provider para o Azure DevOps, que permite que você crie e gerencie os próprios componentes do Azure DevOps - como projetos, pipelines (builds e releases) variables groups e muito mais.

Esse módulo ainda é relativamente novo (enquanto escrevo este artigo, ele está na versão 0.10.0) e portanto pode não ser muito maduro, mas já é possível fazer muita coisa com ele. No nosso caso, iremos usá-lo para atualizar as variáveis de um grupo de variáveis do Azure DevOps com os dados gerados pelo Terraform. Esse grupo de variáveis, por sua vez, será usado no momento da implantação da aplicação.

Para criar e manter um grupo de variáveis - e seus respectivos valores - no Azure DevOps, temos dois caminhos possíveis:

1.Criar o grupo de variáveis e seus valores diretamente no Azure DevOps. Essa é a forma “tradicional” de manter as variáveis - elas são criadas e mantidas diretamente no Azure DevOps, sem nenhuma dependência externa.

  1. Criar o grupo de variáveis e vinculá-lo a um Key Vault do Azure. Essa solução se torna mais interessante quando os conteúdos das variáveis podem ser usados não apenas durante os deployments, mas também fora do Azure DevOps - por exemplo, como parte de uma implantação de um template ARM ou até mesmo como um ConfigMap de um cluster AKS.

No nosso caso, vamos usar a primeira opção, que é mais simples e direta.

Criando o ambiente da aplicação

Para demonstrar como atualizar as variáveis do Azure DevOps com o Terraform, vamos criar um ambiente de aplicação ASP.NET Core no Azure. Para isso, considere o seguinte código Terraform (simplificado para fins didáticos):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Resource Group

resource "azurerm_resource_group" "rg" {
  name                = "rg-demo"
  location            = "brazilsouth"
}

# App Service Plan

resource "azurerm_service_plan" "web" {
  name                = "demo-svcplan"
  #...
}

# Web app

resource "azurerm_windows_web_app" "web" {
  name                = "demo-webapp"
  service_plan_id     = azurerm_service_plan.web.id
  #...
}

# Log Analytics Workspace

resource "azurerm_log_analytics_workspace" "la" {
  name                = "demo-la"
  #...
}

# Application Insights

resource "azurerm_application_insights" "ai" {
  name                = "demo-ai"
  workspace_id        = azurerm_log_analytics_workspace.la.id
  #...
}

# SQL Server

resource "azurerm_mssql_server" "sql" {
  name                         = "demo-sql"
  #...
}

# SQL Database

resource "azurerm_mssql_database" "db" {
  name                         = "demo-db"
  server_name                  = azurerm_sql_server.sql.name
  #...
}

Esse código cria um grupo de recursos no Azure, que contém um App Service Plan, um App Service, um Application Insights, um Log Analytics Workspace, um SQL Server e um SQL Database.

Quando formos fazer o deployment da nossa aplicação, vamos precisar:

  • Da string de conexão para o banco de dados
  • Da chave de instrumentação para o Application Insights

Vamos ver como obter esses dados em tempo de provisionamento dos recursos.

Obtendo os dados dos recursos provisionados

Quando você provisiona no Terraform recursos que produzem dados que só serão conhecidos após o provisionamento, esses dados são tipicamente expostos na forma de atributos exportados. Por exemplo, se você provisionar Application Insights, você pode obter a chave de instrumentação usando o atributo instrumentation_key:

1
2
3
4
outputs {
    # Obtém a chave de instrumentação do Application Insights e expõe como um valor de saída
    ai_key = azurerm_application_insights.ai.instrumentation_key
}

Ainda que a gente possa usar o recurso de variáveis de saída do Terraform para expor esses dados, isso não é muito seguro. Afinal, esses dados são sensíveis e não deveriam ser expostos dessa forma - os valores de saída do Terraform são expostos abertamente tanto no console quanto no arquivo de state.

É por isso que a gente vai usar o provedor do Azure DevOps - para pegar esses valores e jogar diretamente numa variável secreta (criptografada) do Azure DevOps, sem precisar expor esse valor de maneira alguma. Prático e seguro!

Criando o grupo de variáveis no Azure DevOps via Terraform

Para criar o grupo de variáveis no Azure DevOps, vamos usar o recurso azuredevops_variable_group do provedor do Azure DevOps. Esse recurso permite que você crie e gerencie grupos de variáveis no Azure DevOps.

No nosso exemplo, assumindo que queremos criar um grupo de variáveis chamado demo-variables num team project pré-existente chamado demo-project, podemos usar o seguinte código:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
data "azuredevops_project" "tp" {
  name = "demo-project"
}

resource "azuredevops_variable_group" "vg" {
  project_id = data.azuredevops_project.tp.id
  name       = "demo-variables"
  allow_access = true

  variable {
    name = "webapp_name"
    value = azurerm_windows_web_app.web.name
  }

  variable {
    name = "svcplan_name"
    value = azurerm_service_plan.web.name
  }

  variable {
    name = "ai_key"
    secret_value = azurerm_application_insights.ai.instrumentation_key
    is_secret = true
  }

  variable {
    name = "ai_conn_string"
    secret_value = azurerm_application_insights.ai.connection_string
    is_secret = true
  }

  variable {
    name = "db_conn_string"
    secret_value = "Data Source=${azurerm_mssql_server.sql.fully_qualified_domain_name};Initial Catalog=${azurerm_mssql_database.db.name};User Id=${azurerm_mssql_database.db.administrator_login}@${azurerm_mssql_server.sql.name};Password=${azurerm_mssql_database.db.administrator_login_password};"
    is_secret = true
  }
}

Repare que criamos variáveis normais (não-secretas) chamadas webapp_name e svcplan_name, que contém o nome do App Service e do App Service Plan, respectivamente. E, além disso, criamos também as variáveis secretas para guardar a chave de instrumentação do Application Insights (ai_key) e sua string de conexão (ai_conn_string), bem como a string de conexão para o banco de dados (db_conn_string).

Montando a string de conexão do SQL Server

A string de conexão do SQL Server merece uma menção à parte. Isso porque o Azure SQL não oferece um atributo que nos dê a string de conexão pronta - diferentemente do que acontece com o Application Insights. Entre outras coisas, porque a string de conexão depende da linguagem de programação e do framework de acesso a dados que você está usando.

É por isso que, no exemplo anterior, montamos uma string de conexão no formato esperado pelo ASP.NET Core, aplicando os atributos fully_qualified_domain_name, administrator_login e administrator_login_password do SQL Server, e o atributo name do SQL Database.

DICA: Uma dica legal com relação à senha do administrador do SQL Server: ao invés de “chumbar” um valor fixo, você pode usar o recurso random_password do Terraform para gerar uma senha aleatória. E como a senha passa a ser aleatória - ou seja, muda a cada novo deployment - a gente salva essa senha como parte da connection string que foi colocada na variável db_conn_string do Azure DevOps. E ninguém precisa saber qual é a senha que foi gerada! Coloque o trecho de código abaixo para gerar uma senha aleatória para seu SQL Server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# SQL Server

resource "random_password" "db" {
  length           = 16
  special          = true
}

resource "azurerm_mssql_server" "db" {
  name                = "demo-sql"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  version             = "12.0"
  administrator_login = "sqladmin"
  administrator_login_password = random_password.db.result
}

No nosso exemplo escolhemos usar um nome de usuário hardcoded, mas mesmo o nome de usuário poderia ter sido gerado dinamicamente usando os recursos random_string, random_id, random_uuid ou mesmo o random_pet do Terraform.

Usando as variáveis no pipeline

Quando você rodar o seu Terraform, o resultado no Azure DevOps deve ser algo assim:

Grupo de variável criado através do Terraform, com os valores das variáveis inseridas automaticamente a partir dos valores dos recursos
Grupo de variável criado através do Terraform, com os valores das variáveis inseridas automaticamente a partir dos valores dos recursos (clique para ampliar)

Para usar esse grupo de variáveis numa pipeline, basta seguir o procedimento típico:

  1. Para builds/releases clássicos: No editor da pipeline (tanto de build quanto de release), vá na aba Variables e clique em Link variable group. Selecione o grupo de variáveis que você criou e clique em Link.
  2. Para YAML pipelines: No arquivo YAML da pipeline, adicione o seguinte trecho:
1
2
variables:
- group: demo-variables

Pronto! Agora você pode usar as variáveis normalmente no seu pipeline, como se elas tivessem sido criadas manualmente no Azure DevOps.

Toda vez que sua infraestrutura for alterada, o Terraform irá atualizar os dados das variáveis. Daí, é só fazer um novo deployment da aplicação para pegar os valores atualizados!

DICA: Você sabia que dá para encadear pipelines? Com isso, é possível rodar a pipeline de build/release da aplicação toda vez que a infraestrutura for atualizada! Para saber mais, veja na documentação oficial como criar um gatilho para um recurso do tipo Pipeline.

Conclusão

Neste artigo, vimos como usar o provedor do Azure DevOps para atualizar variáveis do Azure DevOps com o Terraform. Com isso, você pode usar o Terraform para provisionar recursos de nuvem e, ao mesmo tempo, atualizar as variáveis do Azure DevOps com os dados gerados dinamicamente por esses recursos.

Isso é especialmente útil quando você precisa usar esses dados no momento do deployment da aplicação, como é o caso da string de conexão para o banco de dados ou a chave de instrumentação para o Application Insights.

Espero que este artigo tenha sido útil para você. Até a próxima!

Um abraço,
Igor



10/11/2023 | Por Igor Abade V. Leite | Em Técnico | Tempo de leitura: 8 mins.

Postagens relacionadas