Развертывание AZURE в нескольких средах с помощью Terraform и GitHub (часть 1)

Обзор

В этом руководстве используются примеры из следующего репозитория шаблонов демо-проектов GitHub.

Добро пожаловать в первую часть моей серии из двух частей: Использование Terraform на GitHub.

Я давно хотел написать учебник, демонстрирующий, как выполнять крупномасштабные развертывания Terraform в Azure, используя немонолитный подход. Я видел, как многие крупные развертывания попадали в одну и ту же ловушку использования одной большой монолитной конфигурации при развертывании в масштабе. Сваливание всего в одну громоздкую конфигурацию может быть проблематичным по многим причинам. Вот лишь некоторые из них:

  • Внесение небольшого изменения может привести к непреднамеренной поломке чего-то более крупного в другом месте конфигурации.
  • Время сборки, а также terraform plan/apply увеличивается. Крошечное изменение может занять много времени, поскольку проверяется все состояние.
  • Это может стать обременительным и сложным для команды или членов команды, чтобы понять всю кодовую базу.
  • Отладка версий и зависимостей модулей и провайдеров в этой парадигме может быть довольно запутанной и может стать ограничивающей.
  • Планирование и внедрение любых изменений становится неуправляемым, рискованным и отнимает много времени.

Существует также множество блогов и руководств о том, как интегрировать Terraform в процессы DevOps CI/CD с помощью Azure DevOps. Поэтому сегодня я решил поделиться с вами тем, как вместо этого использовать Terraform с GitHub.

В этом руководстве мы будем использовать многоразовые рабочие процессы и окружения GitHub для создания многоокружных инфраструктурных развертываний масштаба предприятия в Azure с использованием немонолитного подхода, для построения и упрощения сложных развертываний Terraform в более простые управляемые рабочие потоки, которые можно обновлять независимо друг от друга, увеличения времени сборки и сокращения дублирования кода рабочего процесса за счет использования многоразовых рабочих процессов GitHub.

Что вы получите из этой 2 части учебника:

  • Узнаете о многоразовых рабочих процессах GitHub.
  • Узнаете о действиях GitHub.
  • Узнаете, как интегрировать развертывание terraform с CI/CD с помощью GitHub.
  • Узнайте, как развертывать ресурсы в AZURE в масштабе с помощью GitHub.
  • Узнайте о многоэтапных развертываниях и утверждениях с помощью GitHub Environments.
  • Узнайте, как создавать модули Terraform, используя немонолитный подход.

В качестве дополнительного бонуса я добавил сканирование безопасности IaC с помощью TFSEC, чтобы продемонстрировать сканирование безопасности IaC и проверку качества кода в рамках процесса CI/CD для выявления любых уязвимостей Terraform/Azure или неправильных конфигураций в коде Terraform. Результаты сканирования публикуются на вкладке GitHub Projects Security.

Надеемся, что вы сможете использовать эти концепции в своей организации для создания AZURE-инфраструктуры в масштабе и преуспеть в своих потрясающих облачных проектах.

Предварительные условия

Для начала мы создадим несколько предварительных условий, которые необходимы для интеграции нашего проекта GitHub и рабочих процессов с AZURE, прежде чем мы сможем начать создавать ресурсы.

Мы выполним следующие шаги:

  1. Создание ресурсов Azure (Terraform Backend): (Необязательно) Сначала мы создадим несколько ресурсов, на которых будет размещена наша конфигурация состояния бэкенда Terraform. Нам понадобятся группа ресурсов, учетная запись хранилища и хранилище ключей. Мы также создадим Azure Active Directory App & Service Principal, который будет иметь доступ к нашему бэкенду Terraform и подписке в Azure. Мы свяжем этот принцип службы с нашим проектом GitHub и рабочими процессами позже в этом учебнике.
  2. Создайте репозиторий GitHub: Мы создадим проект GitHub и настроим соответствующие секреты и (необязательно) окружения GitHub, которые мы будем использовать. В проекте будут размещены наши рабочие процессы и конфигурации terraform.
  3. Создайте модули Terraform (модульные): Мы создадим несколько ROOT-модулей Terraform. Отдельные и модульные друг от друга (немонолитные).
  4. Создайте рабочие процессы GitHub: После настройки репозитория и модулей terraform ROOT мы создадим наши рабочие процессы многократного использования и настроим многоэтапные развертывания для запуска и развертывания ресурсов в Azure на основе наших модулей terraform ROOT.

1. Создание ресурсов Azure (Terraform Backend)

Чтобы настроить ресурсы, которые будут действовать как наш бэкенд Terraform, я написал сценарий PowerShell с использованием AZ CLI, который создаст и настроит все и сохранит соответствующие данные/секреты, необходимые для связи с нашим проектом GitHub, в хранилище ключей. Вы можете найти сценарий на моей странице кода на github: AZ-GH-TF-Pre-Reqs.ps1.

Сначала мы войдем в Azure, выполнив команду :

az login
Войти в полноэкранный режим Выйти из полноэкранного режима

После входа в Azure и выбора подписки мы можем запустить сценарий, который создаст все предварительные требования, которые нам понадобятся:

## code/AZ-GH-TF-Pre-Reqs.ps1

#Log into Azure
#az login

# Setup Variables.
$randomInt = Get-Random -Maximum 9999
$subscriptionId=$(az account show --query id -o tsv)
$resourceGroupName = "Demo-Terraform-Core-Backend-RG"
$storageName = "tfcorebackendsa$randomInt"
$kvName = "tf-core-backend-kv$randomInt"
$appName="tf-core-github-SPN$randomInt"
$region = "uksouth"

# Create a resource resourceGroupName
az group create --name "$resourceGroupName" --location "$region"

# Create a Key Vault
az keyvault create `
    --name "$kvName" `
    --resource-group "$resourceGroupName" `
    --location "$region" `
    --enable-rbac-authorization

# Authorize the operation to create a few secrets - Signed in User (Key Vault Secrets Officer)
az ad signed-in-user show --query id -o tsv | foreach-object {
    az role assignment create `
        --role "Key Vault Secrets Officer" `
        --assignee "$_" `
        --scope "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.KeyVault/vaults/$kvName"
    }

# Create an azure storage account - Terraform Backend Storage Account
az storage account create `
    --name "$storageName" `
    --location "$region" `
    --resource-group "$resourceGroupName" `
    --sku "Standard_LRS" `
    --kind "StorageV2" `
    --https-only true `
    --min-tls-version "TLS1_2"

# Authorize the operation to create the container - Signed in User (Storage Blob Data Contributor Role)
az ad signed-in-user show --query id -o tsv | foreach-object {
    az role assignment create `
        --role "Storage Blob Data Contributor" `
        --assignee "$_" `
        --scope "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageName"
    }

#Create Upload container in storage account to store terraform state files
Start-Sleep -s 40
az storage container create `
    --account-name "$storageName" `
    --name "tfstate" `
    --auth-mode login

# Create Terraform Service Principal and assign RBAC Role on Key Vault
$spnJSON = az ad sp create-for-rbac --name $appName `
    --role "Key Vault Secrets Officer" `
    --scopes /subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.KeyVault/vaults/$kvName

# Save new Terraform Service Principal details to key vault
$spnObj = $spnJSON | ConvertFrom-Json
foreach($object_properties in $spnObj.psobject.properties) {
    If ($object_properties.Name -eq "appId") {
        $null = az keyvault secret set --vault-name $kvName --name "ARM-CLIENT-ID" --value $object_properties.Value
    }
    If ($object_properties.Name -eq "password") {
        $null = az keyvault secret set --vault-name $kvName --name "ARM-CLIENT-SECRET" --value $object_properties.Value
    }
    If ($object_properties.Name -eq "tenant") {
        $null = az keyvault secret set --vault-name $kvName --name "ARM-TENANT-ID" --value $object_properties.Value
    }
}
$null = az keyvault secret set --vault-name $kvName --name "ARM-SUBSCRIPTION-ID" --value $subscriptionId

# Assign additional RBAC role to Terraform Service Principal Subscription as Contributor and access to backend storage
az ad sp list --display-name $appName --query [].appId -o tsv | ForEach-Object {
    az role assignment create --assignee "$_" `
        --role "Contributor" `
        --subscription $subscriptionId

    az role assignment create --assignee "$_" `
        --role "Storage Blob Data Contributor" `
        --scope "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageName" `
    }

Войти в полноэкранный режим Выход из полноэкранного режима

Рассмотрим подробнее, шаг за шагом, что делает вышеприведенный сценарий в рамках настройки среды Terraform backend.

  1. Создайте группу ресурсов под названием Demo-Terraform-Core-Backend-RG, содержащую хранилище ключей Azure и учетную запись хранения.
  2. Создайте AAD App and Service Principal, который имеет доступ к хранилищу ключей, учетной записи хранения бэкенда, контейнеру и подписке.
  3. Данные AAD App and Service Principal сохраняются внутри хранилища ключей.

2. Создайте репозиторий GitHub

Для этого шага я создал шаблонный репозиторий, который содержит все необходимое для начала работы. Не стесняйтесь создать свой репозиторий на основе моего шаблона, выбрав Use this template. (Необязательно)

После создания репозитория GitHub есть несколько вещей, которые необходимо установить на репозиторий, прежде чем мы сможем начать его использовать.

  1. Добавьте секреты, которые были созданы в шаге Key Vault выше, во вновь созданный репозиторий GitHub в качестве Repository Secrets
  2. Этот шаг необязателен. Создайте следующие окружения GitHub или окружения, соответствующие вашим собственным требованиям. В моем случае это: Development, UserAcceptanceTesting, Production. Вам не обязательно устанавливать и использовать окружения GitHub, это необязательно и используется в данном руководстве для демонстрации утверждения развертывания с помощью правил защиты.

ПРИМЕЧАНИЕ: Среды GitHub и правила защиты доступны для публичных репозиториев, но для частных репозиториев вам понадобится GitHub Enterprise.

Обратите внимание, что в производственной среде я настроил обязательного рецензента. Это позволит мне установить явных рецензентов, которые должны физически утверждать развертывание в производственной среде. Чтобы узнать больше об утверждениях, смотрите раздел Правила защиты среды.

ПРИМЕЧАНИЕ: Вы также можете настроить секреты GitHub на уровне среды, если у вас есть отдельные принципы обслуживания или даже отдельные подписки в Azure для каждой среды. (Пример: Ваши ресурсы разработки находятся в подписке A, а ресурсы производства — в подписке B). Подробнее см. в разделе Создание зашифрованных секретов для среды.

3. Создаем модули Terraform (модульные)

Теперь, когда наш репозиторий настроен и готов к работе, мы можем начать создавать модульные конфигурации Terraform, или, другими словами, отдельные независимые конфигурации развертывания на основе модулей ROOT Terraform. Если вы посмотрите на демонстрационный репозиторий, то увидите, что в корне репозитория у меня есть пути/папки, которые пронумерованы, например, ./01_Foundation и ./02_Storage.

Каждый из этих путей содержит модуль terraform ROOT, который состоит из набора элементов, которые могут быть независимо настроены и развернуты. Вы не обязаны использовать ту же нумерацию, которую выбрал я, но идея в том, чтобы понять, что эти пути/папки представляют уникальную независимую модульную конфигурацию терраформы, состоящую из набора ресурсов, которые мы хотим развернуть независимо друг от друга.

Итак, в нашем примере

  • путь: ./01_Foundation содержит модуль terraform ROOT/конфигурацию группы ресурсов Azure и хранилища ключей.
  • путь: ./02_Storage содержит модуль terraform ROOT/конфигурацию для одного аккаунта хранения General-V2 и одного аккаунта хранения Data Lake V2.

ПРИМЕЧАНИЕ: Вы также заметите, что каждый модуль ROOT содержит 3x отдельных файла TFVARS: config-dev.tfvars, config-uat.tfvars и config-prod.tfvars. Каждый из них представляет среду. Это потому, что каждая из моих сред будет использовать одну и ту же конфигурацию: foundation_resources.tf, но могут иметь немного разные значения конфигурации или именования.

Пример: Имя группы ресурсов разработки будет называться Demo-Infra-Dev-Rg, в то время как группа ресурсов производства будет называться Demo-Infra-Prod-Rg.

4. Создайте рабочие процессы GitHub

Далее мы создадим специальную структуру папок/путей в корне нашего репозитория под названием .github/workflows. Эта папка/путь будет содержать наши рабочие процессы GitHub Action Workflows.

Вы заметите, что рабочие процессы пронумерованы: ./.github/workflows/01_Foundation.yml и ./.github/workflows/02_Storage.yml, это вызывающие рабочие процессы. Каждый вызывающий рабочий процесс представляет собой модуль терраформы и называется так же, как путь, содержащий модуль терраформы ROOT, как описано в разделе выше. Существуют также 2x GitHub Reusable Workflows под названием ./.github/workflows/az_tf_plan.yml и ./.github/workflows/az_tf_apply.yml.

Давайте подробнее рассмотрим рабочие процессы многократного использования:

  • az_tf_plan.yml:

Этот рабочий процесс является многоразовым рабочим процессом для планирования развертывания терраформы, создания артефакта и загрузки этого артефакта в артефакты рабочего процесса для использования.

### Reusable workflow to plan terraform deployment, create artifact and upload to workflow artifacts for consumption ###
name: 'Build_TF_Plan'
on:
  workflow_call:
    inputs:
      path:
        description: 'Specifies the path of the root terraform module.'
        required: true
        type: string
      tf_version:
        description: 'Specifies version of Terraform to use. e.g: 1.1.0 Default=latest.'
        required: false
        type: string
        default: latest
      az_resource_group:
        description: 'Specifies the Azure Resource Group where the backend storage account is hosted.'
        required: true
        type: string
      az_storage_acc:
        description: 'Specifies the Azure Storage Account where the backend state is hosted.'
        required: true
        type: string
      az_container_name:
        description: 'Specifies the Azure Storage account container where backend Terraform state is hosted.'
        required: true
        type: string
      tf_key:
        description: 'Specifies the Terraform state file name for this plan. Workflow artifact will use same name'
        required: true
        type: string
      gh_environment:
        description: 'Specifies the GitHub deployment environment.'
        required: false
        type: string
        default: null
      tf_vars_file:
        description: 'Specifies the Terraform TFVARS file.'
        required: true
        type: string
      enable_TFSEC:
        description: '(Optional) Enables TFSEC IaC scans and code quality checks on Terraform configurations'
        required: false
        type: boolean
        default: false
    secrets:
      arm_client_id:
        description: 'Specifies the Azure ARM CLIENT ID.'
        required: true
      arm_client_secret:
        description: 'Specifies the Azure ARM CLIENT SECRET.'
        required: true
      arm_subscription_id:
        description: 'Specifies the Azure ARM SUBSCRIPTION ID.'
        required: true
      arm_tenant_id:
        description: 'Specifies the Azure ARM TENANT ID.'
        required: true

jobs:
  build-plan:
    runs-on: ubuntu-latest
    environment: ${{ inputs.gh_environment }}
    defaults:
      run:
        shell: bash
        working-directory: ${{ inputs.path }}
    env:
      STORAGE_ACCOUNT: ${{ inputs.az_storage_acc }}
      CONTAINER_NAME: ${{ inputs.az_container_name }}
      RESOURCE_GROUP: ${{ inputs.az_resource_group }}
      TF_KEY: ${{ inputs.tf_key }}.tfstate
      TF_VARS: ${{ inputs.tf_vars_file }}
      ###AZURE Client details###
      ARM_CLIENT_ID: ${{ secrets.arm_client_id }}
      ARM_CLIENT_SECRET: ${{ secrets.arm_client_secret }}
      ARM_SUBSCRIPTION_ID: ${{ secrets.arm_subscription_id }}
      ARM_TENANT_ID: ${{ secrets.arm_tenant_id }}

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Scan IaC - tfsec
        if: ${{ inputs.ENABLE_TFSEC == 'true' }}
        uses: aquasecurity/tfsec-sarif-action@v0.1.3
        with:
          sarif_file: tfsec.sarif

      - name: Upload SARIF file
        if: ${{ inputs.ENABLE_TFSEC == 'true' }}
        uses: github/codeql-action/upload-sarif@v1
        with:
          sarif_file: tfsec.sarif

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1.3.2
        with:
          terraform_version: ${{ inputs.tf_version }}

      - name: Terraform Format
        id: fmt
        run: terraform fmt --check

      - name: Terraform Init
        id: init
        run: terraform init --backend-config="storage_account_name=$STORAGE_ACCOUNT" --backend-config="container_name=$CONTAINER_NAME" --backend-config="resource_group_name=$RESOURCE_GROUP" --backend-config="key=$TF_KEY"

      - name: Terraform Validate
        id: validate
        run: terraform validate

      - name: Terraform Plan
        id: plan
        run: terraform plan --var-file=$TF_VARS --out=plan.tfplan
        continue-on-error: true

      - name: Terraform Plan Status
        if: steps.plan.outcome == 'failure'
        run: exit 1

      - name: Compress TF Plan artifact
        run: zip -r ${{ inputs.tf_key }}.zip ./*

      - name: Upload Artifact
        uses: actions/upload-artifact@v2
        with:
          name: '${{ inputs.tf_key }}'
          path: '${{ inputs.path }}/${{ inputs.tf_key }}.zip'
          retention-days: 5
Вход в полноэкранный режим Выход из полноэкранного режима

ПРИМЕЧАНИЕ: Многоразовый рабочий процесс может быть запущен только другим рабочим процессом, он же вызывающий рабочий процесс. Мы можем видеть это по триггеру on: под названием workflow_call:.

on:
  workflow_call:
Вход в полноэкранный режим Выход из полноэкранного режима

Как вы можете видеть, многократно используемый рабочий процесс может получить определенные входы при вызове вызывающего рабочего процесса. Обратите внимание, что один из входов называется path:, который мы можем использовать для указания пути к модулю ROOT terraform, который мы хотим спланировать и развернуть.

Входы Требуемый Описание По умолчанию
path True Указывает путь к корневому модулю терраформы.
tf_version False (Необязательно) Указывает версию Terraform для использования. например: 1.1.0 По умолчанию = последняя. latest
az_resource_group True Указывает группу ресурсов Azure, в которой размещена учетная запись внутреннего хранилища.
az_storage_acc True Указывает учетную запись хранилища Azure, в которой размещено состояние бэкенда.
az_container_name True Указание контейнера учетной записи Azure Storage, в котором размещается состояние бэкенда Terraform.
tf_key True Указывает имя файла состояния Terraform для этого плана. Артефакт рабочего процесса будет использовать то же имя.
gh_environment False (Необязательно) Указывает среду развертывания GitHub. Не указывайте этот параметр, если у вас не настроено окружение GitHub. null
tf_vars_file True Указывает файл Terraform TFVARS.
enable_TFSEC False Включить сканирование IaC TFSEC, результаты публикуются на вкладке безопасности проекта GitHub. (Для частных репозиториев требуется GitHub enterprise) FALSE

Нам также нужно передать некоторые секреты от вызывающей стороны многократно используемому рабочему процессу. Это данные нашего принципала службы, который мы создали для доступа в Azure, и он связан с секретами репозитория GitHub, которые мы настроили ранее.

Секрет Требуемый Описание
arm_client_id True Указывает идентификатор ARM CLIENT ID в Azure.
arm_client_secret True Указывает СЕКРЕТ КЛИЕНТА Azure ARM.
arm_subscription_id True Указывается идентификатор подписки Azure ARM SUBSCRIPTION ID.
arm_tenant_id True Указывается идентификатор арендатора Azure ARM.

При вызове этого рабочего процесса будут выполнены следующие действия:

  • Проверьте репозиторий кода и установите контекст пути, указанный в качестве входных данных, на путь, содержащий модуль terraform.
  • Просканируйте IaC по указанному пути на наличие уязвимостей или проблем (Опубликовано на вкладке Безопасность GitHub). Для частных репозиториев требуется GitHub enterprise.
  • Установите и используйте версию terraform в соответствии с исходными данными.
  • Проверьте код модуля terraform на формат.
  • Инициализируйте модуль terraform по заданному пути.
  • Валидировать модуль terraform по заданному пути.
  • Создать план терраформы на основе заданного файла TFVARS, указанного на входе.
  • Сжать артефакты плана.
  • Загрузите сжатый план в качестве артефакта рабочего процесса.

Сканирование безопасности IaC (TFSEC)

Дополнительно IaC-сканирование с использованием TFSEC было применено к многоразовому рабочему процессу PLAN, используя вход enable_TFSEC. По умолчанию этот параметр установлен на FALSE.

ПРИМЕЧАНИЕ: Если вы используете приватный репозиторий, вам потребуется учетная запись GitHub Enterprise, чтобы включить сканирование кода с помощью TFSEC. Однако функция сканирования кода включена в любых публичных репозиториях. Если вы используете частный репозиторий и не имеете корпоративной учетной записи, оставьте этот параметр по умолчанию: FALSE и посмотрите мою другую статью в блоге о сканировании IaC с помощью TFSEC для VsCode (расширение).

Каждая конфигурация terraform при вызове многоразового рабочего процесса PLAN будет просканирована на наличие любых уязвимостей и неправильных конфигураций Terraform IaC, а результаты будут опубликованы на вкладке GitHub Projects Security, например:

Сканирование безопасности IaC не остановит и не приведет к неудаче любого плана или развертывания Terraform, но оно призвано выявить проблемы в коде, которые можно рассмотреть и исправить или улучшить.

Давайте посмотрим на наш второй рабочий процесс многократного использования.

  • az_tf_apply.yml:

Этот рабочий процесс предназначен для загрузки артефакта терраформы, построенного по az_tf_plan.yml, и применения артефакта/плана (развертывание запланированной конфигурации терраформы).

### Reusable workflow to download terraform artifact built by `az_tf_plan` and apply the artifact/plan ###
name: 'Apply_TF_Plan'
on:
  workflow_call:
    inputs:
      path:
        description: 'Specifies the path of the root terraform module.'
        required: true
        type: string
      tf_version:
        description: 'Specifies version of Terraform to use. e.g: 1.1.0 Default=latest.'
        required: false
        type: string
        default: latest
      az_resource_group:
        description: 'Specifies the Azure Resource Group where the backend storage account is hosted.'
        required: true
        type: string
      az_storage_acc:
        description: 'Specifies the Azure Storage Account where the backend state is hosted.'
        required: true
        type: string
      az_container_name:
        description: 'Specifies the Azure Storage account container where backend Terraform state is hosted.'
        required: true
        type: string
      tf_key:
        description: 'Specifies the Terraform state file name. Workflow artifact will be the same name.'
        required: true
        type: string
      gh_environment:
        description: 'Specifies the GitHub deployment environment.'
        required: false
        type: string
        default: null
    secrets:
      arm_client_id:
        description: 'Specifies the Azure ARM CLIENT ID.'
        required: true
      arm_client_secret:
        description: 'Specifies the Azure ARM CLIENT SECRET.'
        required: true
      arm_subscription_id:
        description: 'Specifies the Azure ARM SUBSCRIPTION ID.'
        required: true
      arm_tenant_id:
        description: 'Specifies the Azure ARM TENANT ID.'
        required: true

jobs:
  apply-plan:
    runs-on: ubuntu-latest
    environment: ${{ inputs.gh_environment }}
    defaults:
      run:
        shell: bash
        working-directory: ${{ inputs.path }}
    env:
      STORAGE_ACCOUNT: ${{ inputs.az_storage_acc }}
      CONTAINER_NAME: ${{ inputs.az_container_name }}
      RESOURCE_GROUP: ${{ inputs.az_resource_group }}
      TF_KEY: ${{ inputs.tf_key }}.tfstate
      ###AZURE Client details###
      ARM_CLIENT_ID: ${{ secrets.arm_client_id }}
      ARM_CLIENT_SECRET: ${{ secrets.arm_client_secret }}
      ARM_SUBSCRIPTION_ID: ${{ secrets.arm_subscription_id }}
      ARM_TENANT_ID: ${{ secrets.arm_tenant_id }}

    steps:
      - name: Download Artifact
        uses: actions/download-artifact@v2
        with:
          name: ${{ inputs.tf_key }}
          path: ${{ inputs.path }}

      - name: Decompress TF Plan artifact
        run: unzip ${{ inputs.tf_key }}.zip

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1.3.2
        with:
          terraform_version: ${{ inputs.tf_version }}

      - name: Terraform Init
        id: init
        run: terraform init --backend-config="storage_account_name=$STORAGE_ACCOUNT" --backend-config="container_name=$CONTAINER_NAME" --backend-config="resource_group_name=$RESOURCE_GROUP" --backend-config="key=$TF_KEY"

      - name: Terraform Apply
        run: terraform apply plan.tfplan
Вход в полноэкранный режим Выйдите из полноэкранного режима

Входы и секреты почти такие же, как и в нашем предыдущем многоразовом рабочем процессе, который создавал план терраформы.

Входы Требуемый Описание По умолчанию
path True Указывает путь к корневому модулю терраформы.
tf_version False (Необязательно) Указывает версию Terraform для использования. например: 1.1.0 По умолчанию = последняя. latest
az_resource_group True Указывает группу ресурсов Azure, в которой размещена учетная запись внутреннего хранилища.
az_storage_acc True Указывает учетную запись хранилища Azure, в которой размещено состояние бэкенда.
az_container_name True Указание контейнера учетной записи Azure Storage, в котором размещается состояние бэкенда Terraform.
tf_key True Указывает имя файла состояния Terraform для этого плана. Артефакт рабочего процесса будет иметь такое же имя.
gh_environment False (Необязательно) Указывает среду развертывания GitHub. Не указывайте этот параметр, если у вас не настроено окружение GitHub. null
Секрет Требуется Описание
arm_client_id True Указывает идентификатор Azure ARM CLIENT ID.
arm_client_secret True Указывает СЕКРЕТ КЛИЕНТА Azure ARM.
arm_subscription_id True Указывается идентификатор подписки Azure ARM SUBSCRIPTION ID.
arm_tenant_id True Указывается идентификатор арендатора Azure ARM.

При вызове этого рабочего процесса будут выполнены следующие действия:

  • Загрузите план терраформирования (артефакт рабочего процесса).
  • Распакуйте план терраформирования (артефакт рабочего процесса).
  • Установите и используйте версию terraform в соответствии с исходными данными.
  • Повторная инициализация модуля terraform.
  • Применить конфигурацию terraform на основе плана terraform, который был создан предыдущим рабочим процессом.

Далее рассмотрим один из вызывающих рабочих процессов. Эти рабочие процессы будут использоваться для вызова рабочих процессов многократного использования.

  • 01_Foundation.yml:

Этот рабочий процесс является вызывающим рабочим процессом. Он вызывает и запускает многоразовый рабочий процесс az_tf_plan.yml и создает базовое развертывание terraform PLAN на основе репозитория path: ./01_Foundation, содержащего модуль terraform ROOT/конфигурацию группы ресурсов Azure и хранилища ключей. Артефакты плана проверяются, сжимаются и загружаются в артефакты рабочего процесса, затем вызывающий рабочий процесс 01_Foundation вызывает и запускает второй многоразовый рабочий процесс az_tf_apply.yml, который загружает и распаковывает артефакт PLAN и запускает развертывание на основе плана. (Также показано, как использовать GitHub Environments для многоэтапного развертывания на основе окружения с утверждениями — необязательно)

name: '01_Foundation'
on:
  workflow_dispatch:

jobs:
  Plan_Dev:
    uses: Pwd9000-ML/Azure-Terraform-Deployments/.github/workflows/az_tf_plan.yml@master
    with:
      path: 01_Foundation ## Path to terraform root module (Required)
      tf_version: latest ## Terraform version e.g: 1.1.0 Default=latest (Optional)
      az_resource_group: TF-Core-Rg ## AZ backend - AZURE Resource Group hosting terraform backend storage acc (Required)
      az_storage_acc: tfcorebackendsa ## AZ backend - AZURE terraform backend storage acc (Required)
      az_container_name: ghdeploytfstate ## AZ backend - AZURE storage container hosting state files (Required)
      tf_key: foundation-dev ## AZ backend - Specifies name that will be given to terraform state file and plan artifact (Required)
      tf_vars_file: config-dev.tfvars ## Terraform TFVARS (Required)
      enable_TFSEC: true ## (Optional)  Enable TFSEC IaC scans (Private repo requires GitHub enterprise)
    secrets:
      arm_client_id: ${{ secrets.ARM_CLIENT_ID }} ## ARM Client ID
      arm_client_secret: ${{ secrets.ARM_CLIENT_SECRET }} ## ARM Client Secret
      arm_subscription_id: ${{ secrets.ARM_SUBSCRIPTION_ID }} ## ARM Subscription ID
      arm_tenant_id: ${{ secrets.ARM_TENANT_ID }} ## ARM Tenant ID

  Deploy_Dev:
    needs: Plan_Dev
    uses: Pwd9000-ML/Azure-Terraform-Deployments/.github/workflows/az_tf_apply.yml@master
    with:
      path: 01_Foundation ## Path to terraform root module (Required)
      tf_version: latest ## Terraform version e.g: 1.1.0 Default=latest (Optional)
      az_resource_group: TF-Core-Rg ## AZ backend - AZURE Resource Group hosting terraform backend storage acc (Required)
      az_storage_acc: tfcorebackendsa ## AZ backend - AZURE terraform backend storage acc (Required)
      az_container_name: ghdeploytfstate ## AZ backend - AZURE storage container hosting state files (Required)
      tf_key: foundation-dev ## AZ backend - Specifies name of the terraform state file and workflow artifact to download (Required)
      gh_environment: Development ## GH Environment. Default=null - (Optional)
    secrets:
      arm_client_id: ${{ secrets.ARM_CLIENT_ID }} ## ARM Client ID
      arm_client_secret: ${{ secrets.ARM_CLIENT_SECRET }} ## ARM Client Secret
      arm_subscription_id: ${{ secrets.ARM_SUBSCRIPTION_ID }} ## ARM Subscription ID
      arm_tenant_id: ${{ secrets.ARM_TENANT_ID }} ## ARM Tenant ID

  Plan_Uat:
    uses: Pwd9000-ML/Azure-Terraform-Deployments/.github/workflows/az_tf_plan.yml@master
    with:
      path: 01_Foundation
      az_resource_group: TF-Core-Rg
      az_storage_acc: tfcorebackendsa
      az_container_name: ghdeploytfstate
      tf_key: foundation-uat
      tf_vars_file: config-uat.tfvars
      enable_TFSEC: true
    secrets:
      arm_client_id: ${{ secrets.ARM_CLIENT_ID }}
      arm_client_secret: ${{ secrets.ARM_CLIENT_SECRET }}
      arm_subscription_id: ${{ secrets.ARM_SUBSCRIPTION_ID }}
      arm_tenant_id: ${{ secrets.ARM_TENANT_ID }}

  Deploy_Uat:
    needs: [Plan_Uat, Deploy_Dev]
    uses: Pwd9000-ML/Azure-Terraform-Deployments/.github/workflows/az_tf_apply.yml@master
    with:
      path: 01_Foundation
      az_resource_group: TF-Core-Rg
      az_storage_acc: tfcorebackendsa
      az_container_name: ghdeploytfstate
      tf_key: foundation-uat
      gh_environment: UserAcceptanceTesting
    secrets:
      arm_client_id: ${{ secrets.ARM_CLIENT_ID }}
      arm_client_secret: ${{ secrets.ARM_CLIENT_SECRET }}
      arm_subscription_id: ${{ secrets.ARM_SUBSCRIPTION_ID }}
      arm_tenant_id: ${{ secrets.ARM_TENANT_ID }}

  Plan_Prod:
    uses: Pwd9000-ML/Azure-Terraform-Deployments/.github/workflows/az_tf_plan.yml@master
    with:
      path: 01_Foundation
      tf_version: latest
      az_resource_group: TF-Core-Rg
      az_storage_acc: tfcorebackendsa
      az_container_name: ghdeploytfstate
      tf_key: foundation-prod
      tf_vars_file: config-prod.tfvars
      enable_TFSEC: true
    secrets:
      arm_client_id: ${{ secrets.ARM_CLIENT_ID }}
      arm_client_secret: ${{ secrets.ARM_CLIENT_SECRET }}
      arm_subscription_id: ${{ secrets.ARM_SUBSCRIPTION_ID }}
      arm_tenant_id: ${{ secrets.ARM_TENANT_ID }}

  Deploy_Prod:
    needs: [Plan_Prod, Deploy_Uat]
    uses: Pwd9000-ML/Azure-Terraform-Deployments/.github/workflows/az_tf_apply.yml@master
    with:
      path: 01_Foundation
      az_resource_group: TF-Core-Rg
      az_storage_acc: tfcorebackendsa
      az_container_name: ghdeploytfstate
      tf_key: foundation-prod
      gh_environment: Production
    secrets:
      arm_client_id: ${{ secrets.ARM_CLIENT_ID }}
      arm_client_secret: ${{ secrets.ARM_CLIENT_SECRET }}
      arm_subscription_id: ${{ secrets.ARM_SUBSCRIPTION_ID }}
      arm_tenant_id: ${{ secrets.ARM_TENANT_ID }}
Вход в полноэкранный режим Выход из полноэкранного режима

Обратите внимание, что у нас есть несколько jobs: в рабочем процессе вызывающей стороны, одно задание для генерации плана терраформирования и одно задание для развертывания плана, для каждой среды.

Вы увидите, что каждое задание плана использует различные файлы TFVARS: config-dev.tfvars, config-uat.tfvars и config-prod.tfvars соответственно для каждой среды, но используя одну и ту же конфигурацию модуля ROOT по пути: ./01_Foundation/foundation_resources.tf.

Каждое задание плана также связано с tf_key, который представляет собой имя файла состояния бэкенда, а также имя, данное сжатому загруженному артефакту рабочего процесса, содержащему план терраформирования:

Входы каждого многоразового рабочего процесса указываются на вызывающем рабочем процессе jobs: с помощью with:, и Secrets с помощью secret:.

(Необязательно) — Вы также заметите, что только задания Deploy: Deploy_Dev:, Deploy_Uat:, Deploy_Prod:, связаны с входным gh_environment, который указывает, с какой средой GitHub связано задание. Каждое задание Plan: Plan_Dev:, Plan_Uat:, Plan_Prod:, не связаны ни с каким окружением GitHub.

Если вы не используете окружения GitHub или они у вас не настроены, вы можете опустить ввод: gh_environment полностью. Однако преимущества использования GitHub Environments заключаются в правилах защиты, а также в секретах в масштабе среды.

Каждое задание по развертыванию: Deploy_Dev:, Deploy_Uat:, Deploy_Prod: также связаны с соответствующей настройкой needs: соответствующего плана. Это означает, что задание плана должно быть успешным, прежде чем задание развертывания сможет инициализироваться и запуститься. Задания развертывания также связаны с предыдущими заданиями развертывания с помощью needs:, так что сначала собирается Dev, а в случае успеха за ним следует Uat, а в случае успеха — Prod. Однако, если вы помните, мы настроили правило защиты GitHub на нашей производственной среде, которое должно быть одобрено перед запуском.

ПРИМЕЧАНИЕ: если вы следовали этому руководству шаг за шагом и использовали клонированную копию демонстрационного репозитория, вам нужно будет обновить рабочие процессы caller: ./.github/workflows/01_Foundation.yml и ./.github/workflows/02_Storage.yml со входами, указанными в with:, используя значения вашей среды.

Тестирование

Давайте запустим рабочий процесс: 01_Foundation и посмотрим, что произойдет.

После запуска вы увидите, что каждый план был создан, а конфигурации терраформ DEV и UAT были развернуты в Azure в соответствии с конфигурацией терраформ в path: ./01_Foundation:

После утверждения Production мы видим, что утверждение вызвало развертывание производства, и теперь у нас также есть группа ресурсов производства.

Вы заметите, что каждая группа ресурсов содержит хранилище ключей в соответствии с нашей конфигурацией терраформы фундамента под path: ./01_Foundation.

Давайте запустим рабочий процесс: 02_Storage и после развертывания DEV и UAT также одобрим запуск PRODUCTION.

Теперь вы заметите, что каждая из групп ресурсов наших сред также содержит учетные записи хранилищ согласно конфигурации terraform под path: ./02_Storage.

Наконец, если мы перейдем к учетной записи хранения бэкенда terraform, вы увидите, что на основе tf_key, которые мы дали каждому из наших вызывающих рабочих процессов jobs:, каждое развертывание terraform имеет свой собственный файл состояния для каждого ROOT модуля/коллекции, для каждой среды, что хорошо разделяет файлы состояния конфигурации terraform независимо друг от друга.

Заключение

Следуя той же схеме, что и в этом руководстве, вы можете и дальше расширять свои развертывания Terraform модульным, структурированным, немонолитным способом, создавая больше модулей в отдельных путях, например ./03_ect_ect, и расширять свои облачные развертывания более управляемыми частями.

Вы можете структурировать свои модули/коллекции Terraform таким образом, например, сгруппировать определенные ресурсы вместе, образуя функцию, такую как Foundation или Networking, например, или определенный сервис, такой как Storage или Apps, чтобы при необходимости внесения изменений в IaC для определенной функции или сервиса в крупномасштабной архитектуре эти изменения могли быть реализованы безопасно и независимо.

Я надеюсь, что вам понравилась эта статья и вы узнали что-то новое. Примеры кода, использованные в этой статье, вы можете найти на моей странице GitHub. Вы также можете посмотреть на демо-проект или даже создать свои собственные проекты и рабочие процессы из репозитория шаблонов демо-проектов. ❤️

Автор

Like, share, follow me on: ? GitHub | ? Twitter | ? LinkedIn

Marcel.L

Microsoft DevOps MVP | Cloud Solutions & DevOps Architect | Технический спикер, специализирующийся на технологиях Microsoft, IaC и автоматизации в Azure. Найдите меня на GitHub: https://github.com/Pwd9000-ML

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *