一,引言

  我们都知道在执行部署计划之后,当前目录中就产生了名叫 “terraform.states” 的 Terraform 的状态文件,该文件中记录了已部署资源的状态。默认情况下,在执行部署计划后,Terraform 的状态文件会存储在本地,但是这样往往就造成一些弊端:

(1)不适用团队之间协助,就好比在数据库中对同一条数据进行操作时,就会引起异常

(2)状态文件中包含一些机密信息,会造成一定的机密泄露

(3)如果不慎将本地的状态文件删除掉的话,已执行部署计划的资源的管理将很难在通过 Terraform 进行管理

所以,Terraform 是支持在远端存储状态文件,也就是在 Azure Storage Account 中存储远端状态文件,Terraform 状态的存储是由一个称之为Backend的组件决定的,local state使用的是local backend。并且其他所有的Backend在使用之前都需要在模板中显式定义并通过 terraform init 来实现加载和配置。

--------------------Azure Terraform 系列--------------------

1,Azure Terraform(一)入门简介

2,Azure Terraform(二)语法详解

3,Azure Terraform(三)部署 Web 应用程序

4,Azure Terraform(四)状态文件存储

5,Azure Terraform(五)利用Azure DevOps 实现自动化部署基础资源

6,Azure Terraform(六)Common Module

7,Azure Terraform(七)利用Azure DevOps 实现自动化部署基础资源(补充)

8,Azure Terraform(八)利用Azure DevOps 实现Infra资源和.NET CORE Web 应用程序的持续集成、持续部署

二,正文

1,创建状态文件存储账户

转到Azure Portal 上,点击 “+ Create a resource”,输入 “Storage account“ 进行搜索,并且点击 ”create“

输入以下参数:

Resource group 选择:”Web_Test_TF_RG“

Storage account name:”cnbateterraformstorage“

Location:”(Asia Pacific) East Asia“

Performance:”Standard“

Account kind (账户类型)选择:”BlobStorage“

Replication (复制)选择:”Locally-redundant storage(LRS)“ (本地冗余存储(LRS))

点击 ”Review + create“ 进行创建预校验

校验完成后,点击 ”Create“ 进行创建操作

稍等片刻,等待创建完成后,点击 ”go to resource“ 跳转到资源可以查看创建的资源。

选择 “Blog service =》Containers”,点击页面上的 “+ Container” 添加存储状态文件的 Container

Name:"terraform-state"

Public access level:“Private(no anonymous access)”

点击 “Create” 进行创建。

可以看到刚刚创建容器

复制存储账户的访问密钥,稍后会有用

2,创建 Azure Key Vault(密钥保管库)

回到 Azure Portal 首页,点击 ”+ create a resource“,输入”Key Vault“ 进行搜索,点击 ”Create“ 创建

输入相关参数:

Resource group 选择:”Web_Test_TF_RG“

Key vault name:”cnbate-terraform-kv“

Region:”East Asia“

Pricing tier:”Standard“

点击 ”Review + create“ ,创建预校验。

预校验完成后,点击 ”Create“ 进行创建操作

创建完成后,可以点击 ”Go to resource“ 查看创建好的资源

选择 “Settings=》Secrets”,点击 “+ Generate/Import” 创建、或者导入机密信息

Upload options:“Manual”(手动)

Name:“terraform-stste-storage-key”

Value:复制粘贴刚刚的存储账户访问密钥

创建成功,并且可以查看到刚刚创建的机密信息

3,配置 Terraform 后端,并且测试远程 tf 状态

Terraform 需要配置后端,需要以下参数

(1)storage_account_name :Azure 存储账户名称

terraform init -backend-config="access_key=$(az keyvault secret show --name terraform-stste-storage-key --vault-name cnbate-terraform-kv --query value -o tsv)"

(2)container_name:容器名称

(3)key:存储状态文件的名称

(4)access_key:存储账户访问密钥

大家需要注意的是,我这里将 “access_key” 也就是存储账户访问密钥存放在 Azure Key Vault 中了,想要获取 “access_key” 就得通过 Azure Key Vault 获取。

以下是 Terraform 后端配置

terraform {
  backend "azurerm" {
    storage_account_name = "cnbateterraformstorage"
    container_name       = "terraform-state"
    key                  = "cnbate.terraform.stats"
  }
}

3.1,初始化 Terraform 代码

既然我们没有在 Terraform 后端配置代码块中添加 “access_key” 的信息,那么我们就得在初始化的时候对 ”access_key“ 信息赋值

terraform init -backend-config="access_key=$(az keyvault secret show --name terraform-stste-storage-key --vault-name cnbate-terraform-kv --query value -o tsv)"

然后,我们可以看到执行初始化命令之后的日志输出

同时会在存储账户的容器中生成 Blob 块

并且可以看到当前blob 块的详细信息

LEASE STATUS:解锁状态

LEASE STATE:可用状态

3.2,生成执行计划

terraform plan

以下是执行后输入的日志信息

从中不难看到,在生成执行计划之前,先获取状态锁。(注意,生成执行计划。不会将该执行计划持久化到远程状态存储)

PS D:\Core\Terraform\Azure\terraform_cnbate_traffic manager> terraform plan
Acquiring state lock. This may take a few moments...
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.azurerm_resource_group.cnbate_resource_group: Refreshing state...

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_app_service.cnbate_app_service01 will be created
  + resource "azurerm_app_service" "cnbate_app_service01" {
      + app_service_plan_id               = (known after apply)
      + app_settings                      = {
          + "ASPNETCORE_ENVIRONMENT" = "Production"
        }
      + client_affinity_enabled           = false
      + client_cert_enabled               = false
      + custom_domain_verification_id     = (known after apply)
      + default_site_hostname             = (known after apply)
      + enabled                           = true
      + https_only                        = false
      + id                                = (known after apply)
      + location                          = "eastasia"
      + name                              = "CnBateBlogWeb01"
      + outbound_ip_address_list          = (known after apply)
      + outbound_ip_addresses             = (known after apply)
      + possible_outbound_ip_address_list = (known after apply)
      + possible_outbound_ip_addresses    = (known after apply)
      + resource_group_name               = "Web_Test_TF_RG"
      + site_credential                   = (known after apply)

      + auth_settings {
          + additional_login_params        = (known after apply)
          + allowed_external_redirect_urls = (known after apply)
          + default_provider               = (known after apply)
          + enabled                        = (known after apply)
          + issuer                         = (known after apply)
          + runtime_version                = (known after apply)
          + token_refresh_extension_hours  = (known after apply)
          + token_store_enabled            = (known after apply)
          + unauthenticated_client_action  = (known after apply)

          + active_directory {
              + allowed_audiences = (known after apply)
              + client_id         = (known after apply)
              + client_secret     = (sensitive value)
            }

          + facebook {
              + app_id       = (known after apply)
              + app_secret   = (sensitive value)
              + oauth_scopes = (known after apply)
            }

          + google {
              + client_id     = (known after apply)
              + client_secret = (sensitive value)
              + oauth_scopes  = (known after apply)
            }

          + microsoft {
              + client_id     = (known after apply)
              + client_secret = (sensitive value)
              + oauth_scopes  = (known after apply)
            }

          + twitter {
              + consumer_key    = (known after apply)
              + consumer_secret = (sensitive value)
            }
        }

      + connection_string {
          + name  = (known after apply)
          + type  = (known after apply)
          + value = (sensitive value)
        }

      + identity {
          + identity_ids = (known after apply)
          + principal_id = (known after apply)
          + tenant_id    = (known after apply)
          + type         = (known after apply)
        }

      + logs {
          + detailed_error_messages_enabled = (known after apply)
          + failed_request_tracing_enabled  = (known after apply)

          + application_logs {
              + file_system_level = (known after apply)

              + azure_blob_storage {
                  + level             = (known after apply)
                  + retention_in_days = (known after apply)
                  + sas_url           = (sensitive value)
                }
            }

          + http_logs {
              + azure_blob_storage {
                  + retention_in_days = (known after apply)
                  + sas_url           = (sensitive value)
                }

              + file_system {
                  + retention_in_days = (known after apply)
                  + retention_in_mb   = (known after apply)
                }
            }
        }

      + site_config {
          + always_on                   = (known after apply)
          + app_command_line            = (known after apply)
          + auto_swap_slot_name         = (known after apply)
          + default_documents           = (known after apply)
          + dotnet_framework_version    = (known after apply)
          + ftps_state                  = (known after apply)
          + health_check_path           = (known after apply)
          + http2_enabled               = (known after apply)
          + ip_restriction              = (known after apply)
          + java_container              = (known after apply)
          + java_container_version      = (known after apply)
          + java_version                = (known after apply)
          + linux_fx_version            = (known after apply)
          + local_mysql_enabled         = (known after apply)
          + managed_pipeline_mode       = (known after apply)
          + min_tls_version             = (known after apply)
          + php_version                 = (known after apply)
          + python_version              = (known after apply)
          + remote_debugging_enabled    = (known after apply)
          + remote_debugging_version    = (known after apply)
          + scm_ip_restriction          = (known after apply)
          + scm_type                    = (known after apply)
          + scm_use_main_ip_restriction = (known after apply)
          + use_32_bit_worker_process   = (known after apply)
          + websockets_enabled          = (known after apply)
          + windows_fx_version          = (known after apply)

          + cors {
              + allowed_origins     = (known after apply)
              + support_credentials = (known after apply)
            }
        }

      + source_control {
          + branch             = (known after apply)
          + manual_integration = (known after apply)
          + repo_url           = (known after apply)
          + rollback_enabled   = (known after apply)
          + use_mercurial      = (known after apply)
        }

      + storage_account {
          + access_key   = (sensitive value)
          + account_name = (known after apply)
          + mount_path   = (known after apply)
          + name         = (known after apply)
          + share_name   = (known after apply)
          + type         = (known after apply)
        }
    }

  # azurerm_app_service.cnbate_app_service02 will be created
  + resource "azurerm_app_service" "cnbate_app_service02" {
      + app_service_plan_id               = (known after apply)
      + app_settings                      = {
          + "ASPNETCORE_ENVIRONMENT" = "Production"
        }
      + client_affinity_enabled           = false
      + client_cert_enabled               = false
      + custom_domain_verification_id     = (known after apply)
      + default_site_hostname             = (known after apply)
      + enabled                           = true
      + https_only                        = false
      + id                                = (known after apply)
      + location                          = "southeastasia"
      + name                              = "CnBateBlogWeb02"
      + outbound_ip_address_list          = (known after apply)
      + outbound_ip_addresses             = (known after apply)
      + possible_outbound_ip_address_list = (known after apply)
      + possible_outbound_ip_addresses    = (known after apply)
      + resource_group_name               = "Web_Test_TF_RG"
      + site_credential                   = (known after apply)

      + auth_settings {
          + additional_login_params        = (known after apply)
          + allowed_external_redirect_urls = (known after apply)
          + default_provider               = (known after apply)
          + enabled                        = (known after apply)
          + issuer                         = (known after apply)
          + runtime_version                = (known after apply)
          + token_refresh_extension_hours  = (known after apply)
          + token_store_enabled            = (known after apply)
          + unauthenticated_client_action  = (known after apply)

          + active_directory {
              + allowed_audiences = (known after apply)
              + client_id         = (known after apply)
              + client_secret     = (sensitive value)
            }

          + facebook {
              + app_id       = (known after apply)
              + app_secret   = (sensitive value)
              + oauth_scopes = (known after apply)
            }

          + google {
              + client_id     = (known after apply)
              + client_secret = (sensitive value)
              + oauth_scopes  = (known after apply)
            }

          + microsoft {
              + client_id     = (known after apply)
              + client_secret = (sensitive value)
              + oauth_scopes  = (known after apply)
            }

          + twitter {
              + consumer_key    = (known after apply)
              + consumer_secret = (sensitive value)
            }
        }

      + connection_string {
          + name  = (known after apply)
          + type  = (known after apply)
          + value = (sensitive value)
        }

      + identity {
          + identity_ids = (known after apply)
          + principal_id = (known after apply)
          + tenant_id    = (known after apply)
          + type         = (known after apply)
        }

      + logs {
          + detailed_error_messages_enabled = (known after apply)
          + failed_request_tracing_enabled  = (known after apply)

          + application_logs {
              + file_system_level = (known after apply)

              + azure_blob_storage {
                  + level             = (known after apply)
                  + retention_in_days = (known after apply)
                  + sas_url           = (sensitive value)
                }
            }

          + http_logs {
              + azure_blob_storage {
                  + retention_in_days = (known after apply)
                  + sas_url           = (sensitive value)
                }

              + file_system {
                  + retention_in_days = (known after apply)
                  + retention_in_mb   = (known after apply)
                }
            }
        }

      + site_config {
          + always_on                   = (known after apply)
          + app_command_line            = (known after apply)
          + auto_swap_slot_name         = (known after apply)
          + default_documents           = (known after apply)
          + dotnet_framework_version    = (known after apply)
          + ftps_state                  = (known after apply)
          + health_check_path           = (known after apply)
          + http2_enabled               = (known after apply)
          + ip_restriction              = (known after apply)
          + java_container              = (known after apply)
          + java_container_version      = (known after apply)
          + java_version                = (known after apply)
          + linux_fx_version            = (known after apply)
          + local_mysql_enabled         = (known after apply)
          + managed_pipeline_mode       = (known after apply)
          + min_tls_version             = (known after apply)
          + php_version                 = (known after apply)
          + python_version              = (known after apply)
          + remote_debugging_enabled    = (known after apply)
          + remote_debugging_version    = (known after apply)
          + scm_ip_restriction          = (known after apply)
          + scm_type                    = (known after apply)
          + scm_use_main_ip_restriction = (known after apply)
          + use_32_bit_worker_process   = (known after apply)
          + websockets_enabled          = (known after apply)
          + windows_fx_version          = (known after apply)

          + cors {
              + allowed_origins     = (known after apply)
              + support_credentials = (known after apply)
            }
        }

      + source_control {
          + branch             = (known after apply)
          + manual_integration = (known after apply)
          + repo_url           = (known after apply)
          + rollback_enabled   = (known after apply)
          + use_mercurial      = (known after apply)
        }

      + storage_account {
          + access_key   = (sensitive value)
          + account_name = (known after apply)
          + mount_path   = (known after apply)
          + name         = (known after apply)
          + share_name   = (known after apply)
          + type         = (known after apply)
        }
    }

  # azurerm_app_service_plan.cnbate_app_service_plan01 will be created
  + resource "azurerm_app_service_plan" "cnbate_app_service_plan01" {
      + id                           = (known after apply)
      + kind                         = "Windows"
      + location                     = "eastasia"
      + maximum_elastic_worker_count = (known after apply)
      + maximum_number_of_workers    = (known after apply)
      + name                         = "cnbate_appserviceplan01"
      + resource_group_name          = "Web_Test_TF_RG"

      + sku {
          + capacity = (known after apply)
          + size     = "S1"
          + tier     = "Standard"
        }
    }

  # azurerm_app_service_plan.cnbate_app_service_plan02 will be created
  + resource "azurerm_app_service_plan" "cnbate_app_service_plan02" {
      + id                           = (known after apply)
      + kind                         = "Windows"
      + location                     = "southeastasia"
      + maximum_elastic_worker_count = (known after apply)
      + maximum_number_of_workers    = (known after apply)
      + name                         = "cnbate_appserviceplan02"
      + resource_group_name          = "Web_Test_TF_RG"

      + sku {
          + capacity = (known after apply)
          + size     = "S1"
          + tier     = "Standard"
        }
    }

  # azurerm_traffic_manager_endpoint.cnbate_traffic_manager_endpoint01 will be created
  + resource "azurerm_traffic_manager_endpoint" "cnbate_traffic_manager_endpoint01" {
      + endpoint_location       = (known after apply)
      + endpoint_monitor_status = (known after apply)
      + endpoint_status         = (known after apply)
      + geo_mappings            = [
          + "CN",
        ]
      + id                      = (known after apply)
      + name                    = "cnbateblogweb_webapp01_performance"
      + priority                = (known after apply)
      + profile_name            = "cnbateblogweb"
      + resource_group_name     = "Web_Test_TF_RG"
      + target                  = (known after apply)
      + target_resource_id      = (known after apply)
      + type                    = "azureEndpoints"
      + weight                  = (known after apply)
    }

  # azurerm_traffic_manager_endpoint.cnbate_traffic_manager_endpoint02 will be created
  + resource "azurerm_traffic_manager_endpoint" "cnbate_traffic_manager_endpoint02" {
      + endpoint_location       = (known after apply)
      + endpoint_monitor_status = (known after apply)
      + endpoint_status         = (known after apply)
      + geo_mappings            = [
          + "SG",
        ]
      + id                      = (known after apply)
      + name                    = "cnbateblogweb_webapp02_performance"
      + priority                = (known after apply)
      + profile_name            = "cnbateblogweb"
      + resource_group_name     = "Web_Test_TF_RG"
      + target                  = (known after apply)
      + target_resource_id      = (known after apply)
      + type                    = "azureEndpoints"
      + weight                  = (known after apply)
    }

  # azurerm_traffic_manager_profile.cnbate_traffic_manager_profile will be created
  + resource "azurerm_traffic_manager_profile" "cnbate_traffic_manager_profile" {
      + fqdn                   = (known after apply)
      + id                     = (known after apply)
      + name                   = "cnbateblogweb"
      + profile_status         = (known after apply)
      + resource_group_name    = "Web_Test_TF_RG"
      + tags                   = {
          + "Environment" = "Production"
        }
      + traffic_routing_method = "Geographic"

      + dns_config {
          + relative_name = "cnbateblogweb"
          + ttl           = 60
        }

      + monitor_config {
          + interval_in_seconds          = 30
          + path                         = "/"
          + port                         = 80
          + protocol                     = "http"
          + timeout_in_seconds           = 10
          + tolerated_number_of_failures = 3
        }
    }

Plan: 7 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

PS D:\Core\Terraform\Azure\terraform_cnbate_traffic manager>

3.3,执行部署计划

terraform apply

以下是正在执行部署计划输入日志

同样,也是先获取状态锁

PS D:\Core\Terraform\Azure\terraform_cnbate_traffic manager> terraform plan
Acquiring state lock. This may take a few moments...
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.azurerm_resource_group.cnbate_resource_group: Refreshing state...

在执行的过程中,我们再次登录Azure Portal 中,查看存储账容器中的 Blob 块的状态

LEASE STATUS:已锁定

LEASE STATE:已租用(其实可以理解为 “不可用”)

等待部署计划执行完毕之后,Blob 块的状态又恢复到 “已解锁,可用”

同时,点击图中的 “Edit” 可以看到由 terraform 管理的各自资源状态信息全部写到了当前 Blob 块中

ok,今天的内容就先到此。重要提醒:大家做完测试后,别忘记执行 terraform destroy (销毁部署计划)

*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。φ(゜▽゜*)♪是✌✌✌✌✌

三,结尾

  将状态文件进行远端存到Azure Storage Account 中,Blob 中存储的数据会在保存前进行加密处理,并且一旦配置远端存储模式后,状态文件永远不会存储在本地,这样更加方面团队之间的协作。并且远端存储带来的好处是实现了与资源定义模板管理的解耦,可以让 Terraform 状态脱离本地磁盘而存储,提升了资源状态的安全性。

参考资料:Terraform 官方azurerm 文档

Terraform_Cnbate_Traffic_Manager github:https://github.com/yunqian44/Terraform_Cnbate_Traffic_Manager


本文所分享的内容也存在着很多我自己的一些理解,有理解不到位的,还希望多多包涵,并且指出不足之处。