Github Action + TerraformでAzure リソースをプロビジョニングする
この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。
とある業務にてAzure リソースのプロビジョニングを頻繁に行う必要があり、Terraformを利用してコードを作成しデプロイしていました。しかしながら、Terraformを理解している人が少なく個人に依存し過ぎている傾向がありました。またターミナルからコマンド実行も手間がかかり面倒と感じることがありました。 そこでプロビジョニング作業簡略化を目指しGitHub ActionでTerraformを実行するツールを作成してみましたので今回はその一例を紹介したいと思います。ツールを利用することで誰でも簡単にAzure リソースのプロビジョニングを行うことができます。
ツール要件
- (今回の例として)Azure Kubernates Service(以下AKS)をデプロイしたい
- コマンド操作やTerraform,AKS構築に不慣れな人のためにGUIから簡単に操作できるようにしたい
- ユーザーは必要項目を入力するだけでAKSをプロビジョニングしたい
- 使い終わったら手軽に削除したい
構成図
- Github ActionからTerraformを実行しAzureにAKSを作成する(リソースグループも同時に作成)
- Terraform実行アカウントはAzure サービスプリンシパルを利用する
- Terraform実行時に生成されるtfstateファイルはBlob Strageに保管する
手順
サービスプリンシパルの作成
サブスクリプションへのContributor権限を持つAzure サービスプリンシパルを作成します。出力結果は後の手順にて利用しますので控えときます。
$ az ad sp create-for-rbac --name "gha-terraform-demo-sp" --role contributor --scopes /subscriptions/{SubID} { "appId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "displayName": "gha-terraform-demo-sp", "password": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx" }
Azure Blob Storageの作成
Azure Blob Storageとtfstateファイル保管先となるコンテナーも作成します。
// リソースグループ・Blob Storage作成 $ BLOB_STORAGE_NAME=stghaterraformdemo $ RG_NAME=rg-gha-terraform-demo $ LOCATION=japaneast $ az group create --location ${LOCATION} --resource-group ${RG_NAME} $ az storage account create -n ${BLOB_STORAGE_NAME} -g ${RG_NAME} -l ${LOCATION} // Blob Storage内にコンテナーを作成する $ CONNECTION_STRING=$(az storage account show-connection-string -n ${BLOB_STORAGE_NAME} -g ${RG_NAME} --output tsv) $ CONTAINER_NAME=ghaterraformtfstate $ az storage container create -n ${CONTAINER_NAME} --connection-string ${CONNECTION_STRING}
Github Secretの登録
GithubにSecretを登録します。サービスプリンシパル作成時に控えた出力結果を使用します。
ARM_CLIENT_ID: <appId>
ARM_CLIENT_SECRET: <password>
ARM_SUBSCRIPTION_ID: <利用するAzure サブスクリプションID>
ARM_TENANT_ID: <tenant>
コードをgithubリポジトリにpushする
以下のディレクトリを作成します。
- .github/workflows
- terraform
terraform
ファイル、ディレクトリは以下のように構成します。
- mainディレクトリ: コードのメイン処理、tfstateファイルの管理設定、プロバイダ、バージョンを定義するファイルを配置する
- modulesディレクトリ: 作成するリソースをモジュール別に管理し呼び出すモジュールはmain/main.tfで定義する
terraform
├── main
│ ├── backend.tf
│ ├── main.tf
│ ├── provider.tf
│ └── variable.tf
└── modules
├── kubernetes
│ ├── main.tf
│ └── variable.tf
└── resource_group
├── main.tf
├── output.tf
└── variable.tf
以下のファイルを各ディレクトリに配置します
mainディレクトリ
// backend.tf terraform { backend "azurerm" { resource_group_name = "rg-gha-terraform-demo" ##Blob Storageが存在するリソースグループ storage_account_name = "stghaterraformdemo" ##Blob Storage container_name = "ghaterraformtfstate" ##コンテナ名 } } // provider.tf terraform { required_version = ">= 0.13" required_providers { azurerm = { source = "hashicorp/azurerm" version = "3.18.0" } } } provider "azurerm" { features {} } // main.tf module "rg" { source = "../modules/resource_group" name = "rg-${var.id}" location = var.location } module "aks" { source = "../modules/kubernetes" # common kubernetes config location = var.location resource_group_name = module.rg.name name = "aks-${var.id}" kubernetes_version = var.kubernetes_version network_plugin = "azure" ip_versions = ["IPv4"] # default node_pool config default_node_pool_auto_scaling = true default_node_pool_node_count = 3 default_node_pool_vm_size ="Standard_E4a_v4" default_node_pool_max_count = 5 default_node_pool_min_count = 1 # second node_pool config second_node_pool_enabled = var.second_node_pool_enabled ? 1 : 0 second_node_pool_auto_scaling = true second_node_pool_node_count = 3 second_node_pool_vm_size = "Standard_DS2_v2" second_node_pool_max_count = 5 second_node_pool_min_count = 1 depends_on = [module.rg.rg] } // variable.tf variable "id" {} variable "location" {} variable "kubernetes_version" { default = null } variable "second_node_pool_enabled" { type = bool default = false }
modules/resource_group
// main.tf resource "azurerm_resource_group" "rg" { name = var.name location = var.location } // output.tf output "name" { value = azurerm_resource_group.rg.name } // variable.tf variable "name" {} variable "location" {}
modules/kubernates
// main.tf resource "azurerm_kubernetes_cluster" "aks" { name = var.name location = var.location resource_group_name = var.resource_group_name dns_prefix = "aks-${var.name}-dns" kubernetes_version = "${var.kubernetes_version}" default_node_pool { name = "nodepool" zones = ["1","2","3"] node_count = var.default_node_pool_node_count vm_size = var.default_node_pool_vm_size max_pods = "200" enable_auto_scaling = var.default_node_pool_auto_scaling # If enable_auto_scaling is set to true, max_count and min_count is required. max_count = var.default_node_pool_max_count min_count = var.default_node_pool_min_count } network_profile { network_plugin = var.network_plugin ip_versions = var.ip_versions } identity { type = "SystemAssigned" } } resource "azurerm_kubernetes_cluster_node_pool" "secondnodepool" { count = var.second_node_pool_enabled name = "nodepool2" kubernetes_cluster_id = azurerm_kubernetes_cluster.aks.id node_count = var.second_node_pool_node_count vm_size = var.second_node_pool_vm_size max_pods = "200" enable_auto_scaling = var.second_node_pool_auto_scaling # If enable_auto_scaling is set to true, max_count and min_count is required. max_count = var.second_node_pool_max_count min_count = var.second_node_pool_min_count } // variable.tf variable "location" {} variable "resource_group_name" {} variable "kubernetes_version" { default = "1.26.3" } variable "name" {} variable "default_node_pool_node_count" { default = 3 } variable "default_node_pool_vm_size" { default = "Standard_DS2_v2" } variable "default_node_pool_max_count" { default = 3 } variable "default_node_pool_min_count" { default = 3 } variable "default_node_pool_auto_scaling" { default = false } variable "second_node_pool_enabled" { default = 0 } variable "second_node_pool_node_count" { default = 3 } variable "second_node_pool_vm_size" { default = "Standard_DS2_v2" } variable "second_node_pool_max_count" { default = 3 } variable "second_node_pool_min_count" { default = 3 } variable "second_node_pool_auto_scaling" { default = false } variable "network_plugin" { default = "azure" } variable "ip_versions" { default = ["IPv4"] } variable "network_policy" { default = null }
.github/workflows
実行するworkflowファイルを配置します。今回はAKSのデプロイ/削除をしたいので2つのworkflowファイルを作成します。
deploy.yaml
name: Deploy gha-terraform-demo on: workflow_dispatch: inputs: common_name: description: リソースid required: true type: string default: gha-terraform-demo aks_version: description: AKSのバージョン required: true type: string default: 1.26.3 add_node_pool: description: ノードプールを追加する required: false type: boolean jobs: terraform: name: 'Terraform' env: 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 }} TF_VAR_location: Japan East TF_VAR_id: ${{ inputs.common_name }} TF_VAR_kubernetes_version: ${{ inputs.aks_version }} TF_VAR_second_node_pool_enabled: ${{ inputs.add_node_pool }} runs-on: ubuntu-latest defaults: run: shell: bash steps: - name: Checkout uses: actions/checkout@v2 - name: 'Setup Terraform CLI' uses: hashicorp/setup-terraform@v1 with: terraform_version: 1.4.6 - name: 'Terraform init' run: terraform init -backend-config=key=${TF_VAR_id}.tfstate working-directory: terraform/main - name: 'Terraform plan' run: terraform plan -lock=false working-directory: terraform/main - name: 'Terraform apply' run: terraform apply -auto-approve -lock=false working-directory: terraform/main
destroy.yaml
name: Destroy gha-terraform-demo on: workflow_dispatch: inputs: common_name: description: リソースid required: true type: string default: gha-terraform-demo jobs: terraform: name: 'Terraform' env: 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 }} TF_VAR_location: Japan East TF_VAR_id: ${{ inputs.common_name }} runs-on: ubuntu-latest defaults: run: shell: bash steps: - name: Checkout uses: actions/checkout@v2 - name: 'Setup Terraform CLI' uses: hashicorp/setup-terraform@v1 with: terraform_version: 1.4.6 - name: 'Terraform init' run: terraform init -backend-config=key=${TF_VAR_id}.tfstate working-directory: terraform/main - name: 'Terraform destroy' run: terraform destroy -auto-approve working-directory: terraform/main
Github ActionでWorkflowを実行する
作成したworkflowを実行します。[Run workflow]を押下し必要項目を入力し実行します。しばらくしたらJobが起動し成功(緑)/失敗(赤)の結果が確認できます。
- リソースid: 作成するAzure リソースにユニークな名前をつけます
- AKSのバージョン:使いたいAKS バージョンを指定します
- ノードプールを追加する:オプション機能としてノードプールを追加するかチェックします
Azure上にリソースが作成されていることが確認できます。
使い終わったら削除用workflowを実行してリソースを削除します。
ひとこと
ツールの導入によりプロビジョニングの手順が劇的に簡略化され、その過程で生じる手間やエラーが大幅に削減されました。また作業の効率化に寄与するだけでなく正確性も向上させる効果もあります。
この取り組みを通じて今後もこのような自動化ツールを導入することで作業の効率化や正確性向上を実現したいと思います。