使用Terraform和Docker进行实际的Azure资源管理

2020年12月30日11:18:55 发表评论 29 次浏览

本文概述

在开始之前, 我要感谢伊恩·亨特有关进入现实世界Terraform的一些宝贵技巧。

如果你使用的是主要的云提供商之一来托管你的应用程序, 并且你正在登录Web门户并通过单击按钮来创建关键的基础架构, 那么你将犯下非常昂贵的错误。每个基础设施项目都应通过可执行代码文件创建, 该文件将通过拉取请求过程并提交到版本化的源代码控制系统(如git)中。地貌采用代码优先的方法来创建基础结构资源。

我读过的有关Terraform的大多数文章都没有介绍在实际情况下如何使用它。许多文章都错过了一些基本步骤, 例如远程存储Terraform状态, 并且没有提到Terraform模块。我欢迎在帖子末尾的评论部分中遗漏的任何其他建议。

为什么要使用Terraform?

为什么要使用Terraform而不使用Chef, Puppet, Ansible, SaltStack或CloudFormation等? Terraform非常适合管理云资源。同时, 诸如Ansible之类的工具更多地用于供应软件和机器。我对Terraform感到更自在的原因是你正在使用代码定义基础架构, 而不是无休止的yml配置文件。你可以创建可重用的参数化模块, 就像我在其他语言中所习惯的那样。

不要在本地文件系统上存储Terraform状态

Terraform必须存储有关托管基础结构和配置的状态。 Terraform使用此状态将实际资源映射到你的配置, 跟踪元数据, 并提高大型基础架构的性能。 Terraform状态包括配置中所有资源的设置。默认情况下, Terraform状态存储在本地文件系统中的一个名为terraform.tfstate。我读过的几乎每篇博客文章都没有提到保持Terraform状态的正确方法。地形状态应远程存储。

将Terraform状态存储在Azure Blob存储中

你可以将状态存储在地形云这是一项付费服务​​, 或类似AWS S3.

在此示例中, 我将状态保留为Azure Blob存储.

我们的第一步是创建Azure资源以简化此过程。我将需要在Azure中创建以下资源:

  • Azure资源组–一个容器, 用于存放Azure解决方案的相关资源
  • Azure存储帐户–包含所有Azure存储数据资源
  • Azure Blob存储容器–组织一组Blob, 类似于文件系统中的目录
  • Azure密钥保管库–我们将不需要的所有秘密存储在脚本中并检入源代码控制的位置
  • Azure服务主体–创建用于与应用程序, 托管服务和自动化工具一起使用以访问Azure资源的身份

我们将使用Azure CLI工具。我知道, 我知道我们应该使用Terraform。稍后对此进行更多讨论。

Terraform工作区

在现实世界中, 工件是在特定环境(例如开发, 登台, 生产等)中创建的。Terraform的概念是工作区帮助这个。默认情况下, Terraform从默认工作空间开始, 但是我们将在开发工作空间下创建所有基础结构项。

我们为制作了一个自定义演示.
不完全是。点击这里查看.

使用Terraform和Docker进行实际的Azure资源管理1

Terraform将每个工作空间的状态存储在远程存储中的单独状态文件中:

env:/
    dev/
       state.tfs

创建一个存储帐户

下面的脚本将创建一个资源组, 一个存储帐户和一个存储容器。

#!/bin/bash
RESOURCE_GROUP_NAME=tstate
# $1 is the environment or terraform workspace, dev in this example
STORAGE_ACCOUNT_NAME="tstate$RANDOM$1"
CONTAINER_NAME="tstate$1"

# Create resource group
az group create --name $RESOURCE_GROUP_NAME --location eastus

# Create storage account
az storage account create --resource-group $RESOURCE_GROUP_NAME --name $STORAGE_ACCOUNT_NAME --sku Standard_LRS --encryption-services blob

# Get storage account key
ACCOUNT_KEY=$(az storage account keys list --resource-group $RESOURCE_GROUP_NAME --account-name $STORAGE_ACCOUNT_NAME --query [0].value -o tsv)

# Create blob container
az storage container create --name $CONTAINER_NAME --account-name $STORAGE_ACCOUNT_NAME --account-key $ACCOUNT_KEY

echo "storage_account_name: $STORAGE_ACCOUNT_NAME"
echo "container_name: $CONTAINER_NAME"
echo "access_key: $ACCOUNT_KEY"

这将向STDOUT发出类似这样的信息

storage_account_name: tstate666
container_name: tstate
access_key: wp9AZRTfXPgZ6aKkP94/hTqj/rh9Tsdj8gjlng9mtRSoKm/cpPDR8vNzZExoE/xCSko3yzhcwq+8hj1hsPhlRg==

Anaccess_key生成允许访问存储。如前所述, 我们不想将敏感机密存储在源代码管理中, 而是将它们存储在Azure密钥保管库可以安全地存储和检索应用程序秘密, 例如access_key.

创建密钥保管库存储

Microsoft的官方建议是在每个环境中创建一个密钥保管库存储。

以下脚本创建密钥保管库存储:

if [[ $# -eq 0 ]] ; then
    echo 'you must pass in an environment of dev, staging or production'
    exit 0
fi

vault_name="my-key-vault-$1"

az keyvault create --name $vault_name --resource-group "mystate" --location germanywestcentral

现在, 我们将存储access_key, 密钥库中的存储帐户名和存储容器名:

az keyvault secret set --vault-name "my-key-vault-dev" --name "terraform-backend-key" --value "wp9AZRTfXPgZ6aKkP94/hTqj/rh9Tsdj8gjlng9mtRSoKm/cpPDR8vNzZExoE/xCSko3yzhcwq+8hj1hsPhlRg=="
az keyvault secret set --vault-name "my-key-vault-dev" --name "state-storage-account-name" --value "tstate6298"
az keyvault secret set --vault-name "my-key-vault-dev" --name "state-storage-container-name" --value "tstate"

我还将Azure订阅ID存储在密钥保管库中, 以便于访问:

az keyvault secret set --vault-name "my-key-vault-dev" --name "my-subscription-id" --value "79c15383-4cfc-49my-a234-d1394814ce95"

创建服务主体

下一步是创建服务主体帐户, 我们将在访问应用程序的基础结构时授予其权限。

SUBSCRIPTIONID=$(az keyvault secret show --name my-subscription-id --vault-name my-key-vault --query value -o tsv)
az ad sp create-for-rbac --role contributor --scopes "/subscriptions/$SUBSCRIPTIONID" --name http://myterraform --sdk-auth

上面的脚本将输出类似以下内容的内容:

{
  "clientId": "fd0e2604-c5a2-46e2-93d1-c0d77a8eca65", "clientSecret": "d997c921-5cde-40c8-99db-c71d4a380176", "subscriptionId": "79c15383-4cfc-49my-a234-d1394814ce95", "tenantId": "a567135e-3479-41fd-8acf-a606c8383061", "activeDirectoryEndpointUrl": "https://login.microsoftonline.com", "resourceManagerEndpointUrl": "https://management.azure.com/", "activeDirectoryGraphResourceId": "https://graph.windows.net/", "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", "galleryEndpointUrl": "https://gallery.azure.com/", "managementEndpointUrl": "https://management.core.windows.net/"
}

这是唯一一次你可以看到客户秘密因此, 我们需要将其快速放入AZ key保管库存储-聪明!唯一获得访问权限的方法客户秘密再次是重新生成它:

az keyvault secret set --vault-name "my-key-vault-dev" --name "sp-client-id" --value "e900db02-ab6a-4098-a274-5b91d5f510bb"
az keyvault secret set --vault-name "my-key-vault-dev" --name "sp-client-secret" --value "156c4cdf-23e7-44c0-ad2b-64a6f169b253"<

注意:一种更安全的方法是使用客户端证书。

通过Docker运行Terraform

我们将通过运行Terraform码头工人。你应该问的第一个问题是为什么?

以下是你应该通过Docker运行Terraform的几个原因:

  • Terraform脚本应被视为应用程序代码, 并且应具有可预测的操作系统之类的内容
  • 将所有需求封装在一个映像中
  • 一次构建, 随处运行
  • 如果我们使用容器映像存储库, 则可以对映像进行版本控制
  • 通过使用运行时上下文相关的环境变量等参数化值来部署到不同的环境
  • 当多个开发人员在同一个项目上工作时, 具有一致的部署经验

Terraform Docker文件

以下Docker文件将同时安装Terraform和Azure CLI工具:

FROM ubuntu:19.04

ENV TERRAFORM_VERSION 0.12.19
ENV TERRAFORM_URL https://releases.hashicorp.com/terraform/$TERRAFORM_VERSION/terraform_${TERRAFORM_VERSION}_linux_amd64.zip
ENV AZURE_CLI_VERSION 2.0.77

RUN apt-get update && apt-get install -y \
    curl \
    python3-pip \
    zip

RUN echo 'alias python=python3' >> ~/.bashrc
RUN echo 'alias pip=pip3' >> ~/.bashrc
RUN pip3 install --upgrade pip

RUN curl -o /root/terraform.zip $TERRAFORM_URL && \
   unzip /root/terraform.zip -d /usr/local/bin/ && \
   rm /root/terraform.zip

RUN pip3 install azure-cli==${AZURE_CLI_VERSION}


WORKDIR /workspace

RUN chmod -R  +x .

ENTRYPOINT [ "./ops/help.sh", "-h" ]
CMD ["bash"]

的Docker文件上面的代码将在特定版本上同时安装Terraform和azure-cli。我还喜欢为我的Docker映像提供一个帮助菜单的入口, 以解释Docker映像的功能。

的./ops/help.sh文件看起来像这样:

#!/bin/bash

if [ "$1" == "-h" ] ; then
    cat << EndOfMessage
Usage:
./run.sh [environment] [init|destroy]
e.g.
./run.sh dev init
./run.sh dev destroy
EndOfMessage
    exit 0
fi

构建Terraform Docker映像

下面的脚本将构建图像并为工作空间适当地标记它:

#!/bin/bash

if [[ $# -eq 0 ]] ; then
    echo 'you must pass in an environment of dev, staging or production'
    exit 0
fi

version=$(cat ./terraform/version)
tag="my-azure:${version}-$1"

echo "Building images with default parameters"
docker image build \
  --rm \
  -f ./Dockerfile \
  -t $tag \
  --no-cache \
  .

运行时将适当的工作空间参数作为参数传递./build.sh:

./build.sh dev

运行Terraform Docker映像

运行Terraform时使用Docker的部分原因是允许从具有不同环境变量的同一Dockerfile创建不同的环境或工作空间。

的运行下面的脚本将为此工作区选择正确的密钥保管库存储。该脚本有两个参数, 第一个是工作空间, 第二个是命令在里面or破坏.

#!/bin/bash

if [[ $# -eq 0 ]] ; then
    echo 'you must pass in an environment of dev, staging or production and a command of init, destroy or -h'
    exit 0
fi

vault_name="c2-key-vault-$1"

version=$(cat ./terraform/version)
tag="${version}-$1"

working_directory="${PWD}/terraform"


vault_name="c2-key-vault-$1"
container_name="tf-azure-cli-$1"

case "$2" in
    ("init") command="./ops/init.sh" ;;
    ("destroy") command="./ops/teardown.sh" ;;
    (*) docker run \
          --rm \
          -v $working_directory:/workspace:z \
          --name $container_name \
          -it c2-azure:${tag}
        exit 0;;
esac

echo "about to run $command"

echo "setting environment variables for the $1 environment"

export subscription_id=$(az keyvault secret show --name c2-subscription-id --vault-name $vault_name --query value -o tsv)
export state_storage_account_name=$(az keyvault secret show --name state-storage-account-name --vault-name $vault_name --query value -o tsv)
export state_storage_container_name=$(az keyvault secret show --name state-storage-container-name --vault-name $vault_name --query value -o tsv)
export access_key=$(az keyvault secret show --name terraform-backend-key --vault-name $vault_name --query value -o tsv)
export client_id=$(az keyvault secret show --name sp-client-id --vault-name $vault_name --query value -o tsv)
export client_secret=$(az keyvault secret show --name sp-client-secret --vault-name $vault_name --query value -o tsv)
export tenant_id=$(az account show --query tenantId -o tsv)

docker run \
  --rm \
  -v $working_directory:/workspace:z \
  -e resource_group="c2state" \
  -e subscription_id="${subscription_id}"  \
  -e state_storage_account_name="${state_storage_account_name}" \
  -e state_storage_container_name="${state_storage_container_name}" \
  -e access_key="${access_key}" \
  -e client_id="${client_id}" \
  -e client_secret="${client_secret}" \
  -e tenant_id=${tenant_id} \
  -e workspace=$1 \
  --name $container_name \
  --entrypoint $command \
  -it c2-azure:${tag}

环境变量是通过Azure密钥保管库存储中的值分配的, 随后可通过以下方式在Docker容器中使用:-e通话时切换泊坞窗运行.

主机卷也映射到我们的本地Terraform文件和脚本, 因此容器可以立即接收更改, 从而无需在每次更改后重建映像。

的运行脚本是针对每个工作空间执行的, 并且第二个参数为在里面or破坏最终将委托给地形初始化or地形破坏.

# run.sh takes a workspace argument and a command
./run.sh dev init

结果是致电泊坞窗运行。的–入口点开关用于委派给初始化文件脚本或拆解脚本。下面是初始化文件将创建Azure基础结构的脚本:

!/bin/bash

az login --service-principal -u $client_id -p $client_secret --tenant $tenant_id

export TF_VAR_client_id=$client_id
export TF_VAR_client_secret=$client_secret
export ARM_CLIENT_ID=$client_id
export ARM_CLIENT_SECRET=$client_secret
export ARM_ACCESS_KEY=$access_key
export ARM_SUBSCRIPTION_ID=$subscription_id
export ARM_TENANT_ID=$tenant_id
export TF_VAR_subscription_id=$subscription_id


terraform init \
    -backend-config="storage_account_name=${state_storage_account_name}" \
    -backend-config="container_name=${state_storage_container_name}" \
    -backend-config="access_key=${access_key}" \
    -backend-config="key=my.tfstate.$workspace"

terraform workspace select $workspace || terraform workspace new $workspace

terraform apply --auto-approve

在此脚本中, 分配了Terraform脚本所需的环境变量。

地形初始化被称为-后端配置开关, 指示Terraform将状态存储在本文开头创建的Azure Blob存储容器中。

在应用配置之前, 将设置当前Terraform工作空间。

terraform apply –自动批准做创建资源的实际工作。

然后, Terraform将执行主文件文件并表现正常。

破坏

的运行脚本可以用销毁命令:

./run.sh dev destroy

容器将执行此拆解脚本:

#!/bin/bash

echo "tearing the whole $workspace down"

az login --service-principal -u $client_id -p $client_secret --tenant $tenant_id

export TF_VAR_client_id=$client_id
export TF_VAR_client_secret=$client_secret
export ARM_CLIENT_ID=$client_id
export ARM_CLIENT_SECRET=$client_secret
export ARM_ACCESS_KEY=$access_key
export ARM_SUBSCRIPTION_ID=$subscription_id
export ARM_TENANT_ID=$tenant_id
export TF_VAR_subscription_id=$subscription_id  

terraform workspace select $workspace

terraform destroy --auto-approve

什么上升, 可以下降。

地形模块

我看不到足够的提及地形模块在我读过的大多数帖子中。

Terraform模块既可以接受输入变量形式的参数, 也可以接受其他Terraform模块称为输出变量的返回值。

下面的Terraform模块接受两个输入变量resource_group_name和resource_group_location用于创建Azure资源组的资源:

variable "resource_group_name" {
  type                      = string
}

variable "resource_group_location" {
  type                      = string
}

resource "azurerm_resource_group" "main" {
  name      = var.resource_group_name
  location  = var.resource_group_location
}

output "eu_resource_group_name" {
 value      = azurerm_resource_group.main.name
}

output "eu_resource_group_location" {
 value      = azurerm_resource_group.main.location
}

该模块还返回两个输出变量eu_resource_group_name和eu_resource_group_location可以在其他Terraform脚本中使用。

上面的模块这样调用:

module "eu_resource_group" {
  source                        = "./modules/resource_groups"

  resource_group_name           = "${var.resource_group_name}-${terraform.workspace}"
  resource_group_location       = var.location
}

这两个输入变量在模组块。字符串插值用于将当前Terraform工作空间名称添加到资源组名称。所有Azure资源都将在此资源组下创建。

两个输出变量eu_resource_group_name和eu_resource_group_location可以从其他模块中使用:

module "vault" {
  source                        = "./modules/vault"

  resource_group_name           = module.eu_resource_group.eu_resource_group_name
  resource_group_location       = module.eu_resource_group.eu_resource_group_location
}

结语

在阅读许多Terraform帖子时, 我感到非常沮丧, 因为它们太基础了, 无法在实际的, 可用于生产的环境中使用。

甚至Terraform文档也没有详细介绍有关以脚本文件本身以外的其他方式存储密钥和机密的信息, 这是一个很大的安全错误。如果你在实际场景中使用Terraform, 请不要使用本地Terraform状态。

具有输入和输出变量的Terraform模块绝对比一个大型脚本好。

出于与将其他应用程序代码放入容器中完全相同的原因, 在Docker容器中执行Terraform是正确的选择。

日志火箭:全面了解你的网络应用

LogRocket仪表板免费试用横幅

日志火箭是一个前端应用程序监视解决方案, 可让你重播问题, 就好像问题发生在你自己的浏览器中一样。 notlogy无需猜测错误发生的原因, 也不要求用户提供屏幕截图和日志转储, 而是让你重播会话以快速了解出了什么问题。无论框架如何, 它都能与任何应用完美配合, 并具有用于记录来自Redux, Vuex和@ ngrx / store的其他上下文的插件。

除了记录Redux动作和状态外, notlogy还会记录控制台日志, JavaScript错误, 堆栈跟踪, 带有标题+正文, 浏览器元数据和自定义日志的网络请求/响应。它还使用DOM来记录页面上的HTML和CSS, 甚至可以为最复杂的单页面应用程序重新创建像素完美的视频。

免费试用

.

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: