BEMAロゴ

エンジニアの
成長を支援する
技術メディア

【手順解説】GitHub Actions×TerraformでAzure Functionsを自動構築する方法

はじめに

こんにちは、株式会社メンバーズ 生成AI基盤チームの谷森です。

最近、Azureを利用したRAG(Retrieval-Augmented Generation)システムの構築に携わったのですが、気が付けば手作業でのリソース作成や設定が多くなっていました。
そこで今回は、Azureのリソース構築・設定の自動化に挑戦した内容についてご紹介します。

同じような課題を感じている方や、これからインフラの自動化に取り組みたいと考えている若手エンジニアの皆さんの参考になれば幸いです。

自動化に至った背景

今回構築したRAGシステムは、データベースに格納した特定の知識を参照して質問応答を行うというものです。Azure Functionsを中心に様々なAzureサービスを組み合わせて実現しました。しかし、開発を進めるにつれて以下のような課題が顕在化してきました。

  • 環境構築の手間:検証環境・開発環境・本番環境など複数の環境を構築するたびに同じ作業を繰り返す必要がありました。
  • 設定のばらつき:作業者や構築環境が異なると手作業での設定では、環境間で微妙なずれが生じてしまう可能性がありました。
  • 変更への対応:システムの拡張や設定変更が発生した場合、複数のリソースに対して手作業で修正を行うのは時間もかかり、ミスも起こりやすくなる可能性がありました。

これらの課題を解決するためにインフラの自動化が不可欠だと感じました。

自動化対象となるAzureリソース一覧

今回は、RAGシステムを構成するAzureリソースの中でも特にAzure Functionsを動かすために必要な以下のリソースを自動化の対象としました。

  • Azure Functions:サーバーレスコンピューティングのプラットフォームで、イベントドリブン型のアプリケーションを構築するためのサービス(今回はRAGシステムの処理を行うために使用)
  • Azure API Management:バックエンドサービスを統合して一元的に管理するためのサービス(今回はAzure Functionsを管理するために使用)
  • Azure App Service Plan:Azure上でWebアプリケーションやAPIをホストするためのサービス(今回はAzure Functionsをホストするために使用)
  • Azure Storage Account:Azureのストレージサービスを利用するためのアカウント(今回はAzure Functionsの実行ログなどを保存するために使用)
  • Azure Log Analytics Workspace:Azure Monitorの一部で、ログデータを収集・分析・可視化するためのサービス(今回はAzure Functionsのログを確認するために使用)
  • Azure Application Insights:アプリケーションのパフォーマンスや使用状況をリアルタイムで監視するためのサービス(今回はAzure Functionsのログを確認するために使用)

これらのリソース作成を自動化することで、環境構築の効率化・設定の一貫性維持・変更への迅速な対応を目指します。

自動化のための環境準備

インフラの自動化ツールはいくつかありますが、今回はGitHub ActionsとTerraformを選択しました。

1. ローカル環境の準備

①Azure CLIのインストール

下記コマンドでAzure CLIがインストールされているか確認します。

az --version

なければご自身の環境に合わせて下記リンクを参考にインストールします。

②Terraformのインストール

下記コマンドでTerraformがインストールされているか確認します。


terraform -v 

なければご自身の環境に合わせて下記リンクを参考にインストールします。

2. Azureサービスプリンシパルの作成

①Azureログイン

下記コマンドでAzure環境にログインできるか確認します。

az login --use-device-code

上記コマンドを実行すると下記のように表示されるので、実行結果に記載されているリンクとコードでAzure環境にログインします。

$ az login--use-device-code
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code XXXXX to authenticate.

Retrieving tenants and subscriptionsforthe selection...

[Tenant and subscription selection]

No     Subscription name        Subscription ID             Tenant
-----  -----------------------  ----------------------  -----------
[1] *  XXXXXXXXXXXXXXXXXXXXXXX  XXXXXXXXXXXXXXXXXXXXXX  XXXXXXXXXXX

〜〜〜〜〜以下略〜〜〜〜〜

②Azureリソースグループ確認

Azure環境にログインできることを確認したら、下記コマンドでご自身が閲覧可能なAzureリソースグループを確認します。

az group list --query "[].{Name:name, Location:location}" --output table

上記コマンドを実行すると下記のように表示されます。

$ az group list --query "[].{Name:name, Location:location}" --output table
Name        Location
----------  ----------
XXXXXXXXXX  japaneast

③Azureサービスプリンシパル作成

閲覧可能なAzureリソースを確認したら、下記コマンドで閲覧可能なリソースグループ直下にサービスプリンシパルを作成します。

az ad sp create-for-rbac --role="Contributor" --display-name="<任意のサービスプリンシパル名>" --scopes="/subscriptions/<サブスクリプションID>/resourceGroups/<リソースグループ名>"

上記コマンドを実行すると下記のように表示されます。

$ az ad sp create-for-rbac --role="Contributor" --display-name="XXXXX" --scopes="/subscriptions/XXXXX/resourceGroups/XXXXX"
Creating 'Contributor' role assignment under scope '/subscriptions/XXXXX/resourceGroups/XXXXX'
The output includes credentials that you must protect. Be sure that youdonot include these credentialsinyour code or check the credentials into your source control. For more information, see <https://aka.ms/azadsp-cli>
{
  "appId": "XXXXX",
  "displayName": "XXXXX",
  "password": "XXXXX",
  "tenant": "XXXXX"
}

上記コマンド結果で表示されたクライアントシークレットを必ず控えておきます。

Azureポータル上でのサービス プリンシパル表示方法Open in new tab

3. GitHub Secretsの設定

①GitHubリポジトリの作成

GitHub ActionsとTerraform用のコードなどを保存するためにGitHubリポジトリを任意の名前で作成します。

②GitHub Actionsの環境変数設定

GitHubリポジトリのSettingsからサイドバーのSecurity>Secrets and variables>Actionsを選択します。

Repository secretsに任意の環境変数名とクライアントシークレットを登録します。今回はTerraformの設定で取得した以下の4種類のクライアントシークレットを登録します。

任意の環境変数名

クライアントシークレット

AZURE_CLIENT_ID

サービスプリンシパルのService Principal ID

AZURE_SUBSCRIPTION_ID

サービスプリンシパルのSubscription ID

AZURE_TENANT_ID

サービスプリンシパルのTenant ID

CLIENT_SECRET

サービスプリンシパルのService Principal Secret

GitHubリポジトリのディレクトリ構成

GitHubリポジトリを作成したら、下記のようなディレクトリ構成にします。

terraform-rag(任意のGitHubリポジトリ名)
├── .github
│   ├── workflows
│   │   └── terraform-apply.yml(任意のGitHub Actionsコードファイル名)
├── terraform(任意のTerraformコードディレクトリ名)
│   ├── .terraform.lock.hcl
│   ├── main.tf
│   ├── rg.tf
│   └── variables.tf
├── .gitignore
└── README.md

GitHub Actions用のterraform-apply.ymlはGitHubリポジトリのActionsからサイドバーにあるNew workflowをクリックして作成します。

terraformディレクトリとその中にあるmain.tf・rg.tf・variables.tfは手動で作成します。
terraform.lock.hclファイルはterraform initコマンドを実行する度に自動生成・自動更新されるためご自身で作成する必要はありません。

Terraformコード

main.tf

ここでAzureリソースの自動作成コードを記載します。
以下の項目に関してはご自身のお好みで変更して下さい。

  • 識別子
  • リソース名
  • バージョン

var.client・var.environmentに関してはvariables.tfファイルで記載しています。

terraform {
        # Terraformプロバイダとそのバージョン指定(今回はAzureRM4.24.0以上を使用)
        required_providers {
                azurerm = {
                        source  = "hashicorp/azurerm"
                        version = "~> 4.24.0"
                }
        }
        # Terraform自体のバージョン指定(今回は1.10.1以上を使用)
        required_version = ">= 1.10.1"
}
provider "azurerm" {
        skip_provider_registration = true
        features {}
        subscription_id = var.subscription_id
        tenant_id       = var.tenant_id
        client_id       = var.client_id
        client_secret   = var.client_secret
}
resource "azurerm_api_management" "common" {
        name                = "rag-apim-${var.client}-${var.environment}"
        location            = data.azurerm_resource_group.rg.location
        resource_group_name = data.azurerm_resource_group.rg.name
        publisher_name      = "<APIの管理者名>"
        publisher_email     = "<ご自身のメールアドレス>"
        sku_name = "Consumption_0"
}
resource "azurerm_app_service_plan" "insertdb-function" {
        name                = "rag-appservice-${var.client}-${var.environment}"
        location            = data.azurerm_resource_group.rg.location
        resource_group_name = data.azurerm_resource_group.rg.name
        kind                = "Linux"
        reserved            = true
        sku {
                tier = "Standard"
                size = "S1"
        }
}
resource "azurerm_storage_account" "insertdb-function" {
        name                     = "raginsertdb${var.client}${var.environment}"
        resource_group_name      = data.azurerm_resource_group.rg.name
        location                 = data.azurerm_resource_group.rg.location
        account_tier             = "Standard"
        account_replication_type = "LRS"
}
resource "azurerm_function_app" "insertdb-function" {
        name                       = "rag-insertdb-${var.client}-${var.environment}"
        location                   = data.azurerm_resource_group.rg.location
        resource_group_name        = data.azurerm_resource_group.rg.name
        app_service_plan_id        = azurerm_app_service_plan.insertdb-function.id
        storage_account_name       = azurerm_storage_account.insertdb-function.name
        storage_account_access_key = azurerm_storage_account.insertdb-function.primary_access_key
        version                    = "~3"
        site_config {
                linux_fx_version        = "PYTHON|3.11"
        }
}
resource "azurerm_log_analytics_workspace" "insertdb-function" {
        name                = "rag-workspace-${var.client}-${var.environment}"
        location            = data.azurerm_resource_group.rg.location
        resource_group_name = data.azurerm_resource_group.rg.name
        sku                 = "PerGB2018"
        retention_in_days   = 30
}
resource "azurerm_application_insights" "insertdb-function" {
        name                = "rag-insights-${var.client}-${var.environment}"
        location            = data.azurerm_resource_group.rg.location
        resource_group_name = data.azurerm_resource_group.rg.name
        workspace_id        = azurerm_log_analytics_workspace.insertdb-function.id
        # アプリケーションタイプを指定(今回はHTTPベースのアプリケーションを監視するためwebを使用)
        application_type    = "web"
}

各リソースにタグを付けたい場合は下記のようにコードを追加します。

resource "azurerm_api_management" "common" {
        name                = "rag-apim-${var.client}-${var.environment}"
        location            = data.azurerm_resource_group.rg.location
        resource_group_name = data.azurerm_resource_group.rg.name
        publisher_name      = "<ご自身の会社名>"
        publisher_email     = "<ご自身のメールアドレス>"
        sku_name = "Consumption_0"
        tags = {
                AppName = "rag-common-${var.client}-${var.environment}"
                Author = "terraform"
                Client = var.client
                Env = var.environment
                System = "rag"
        }
}

rg.tf

ここで既存リソースグループの情報を取得するコードを記載します。
識別子に関してはご自身のお好みで変更して下さい。

# 既存のリソースグループの情報を取得
data "azurerm_resource_group" "rg" {
  name = var.resource_group_name
}

variables.tf

ここで環境変数を取得するコードを記載します。
以下の変数に関しては私が入力値として使用したいものを設定していますが、設定しなくてもAzureリソース作成自動化はできます。

  • resource_group_name
  • environment
  • client

これらを設定しない場合はmain.tfファイルで記載した各リソース名やタグ内容は任意の文字列に変更します。

variable "subscription_id" {}
variable "client_id" {}
variable "client_secret" {}
variable "tenant_id" {}

# 任意のリージョンをデフォルトで設定可
variable "location" {
  default = "japaneast"
}

# GitHub Actions開始時の入力値を設定可
variable "resource_group_name" {
  default = "<リソースグループ名>"
}
variable "environment" {
  default = "<環境名>"
}
variable "client" {
  default = "<顧客名>"
}

GitHub Actionsコード

ここでmain.tfファイルを実行するコードを記載します。

name: '<フロー名>'
on:
  # 手動トリガーを設定
  workflow_dispatch:  
    inputs:
      resource_group_name:
        description: 'リソースグループ名'
        required: true
        default: '<リソースグループ名>'
      environment:
        description: '環境名'
        required: true
        default: '<環境名>'
      client:
        description: '顧客名'
        required: true
        default: '<顧客名>'
permissions:
  contents: read
  id-token: write
jobs:
  terraform:
    name: '<ジョブ名>'
    runs-on: ubuntu-latest
    environment: production
    steps:
    # リポジトリをGitHub Actionsランナーにチェックアウト
    - name: Checkout
      uses: actions/checkout@v4       
    # Azureにログイン
    - name: 'Az CLI login'
      uses: azure/login@v1
      with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
    # Terraformの指定バージョンをインストール
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v1
      with:
        terraform_version: 1.10.1
    # 新しいまたは既存のTerraform作業ディレクトリの初期ファイルを作成
    - name: Terraform Init
      run: terraform init
      working-directory: terraform
    # Terraformの実行プランを生成
    - name: Terraform Plan
      run: |
        terraform plan \\
          -var "environment=${{ github.event.inputs.environment }}" \\
          -input=false
      working-directory: terraform
    # Terraform設定ファイルに従ってインフラを構築または変更
    - name: Terraform Apply
      env:
        CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
        CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
        SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
      run: |
        terraform apply -auto-approve \\
          -var "resource_group_name=${{ github.event.inputs.resource_group_name }}" \\
          -var "environment=${{ github.event.inputs.environment }}" \\
          -var "client=${{ github.event.inputs.client }}"
      working-directory: terraform

TerraformのstateファイルをAzure内で管理したい場合は、手動でAzure Blob Storageを作成して上記コードの最後に下記コードを追加します。

    # ステートファイルを指定のストレージにアップロード
    - name: Upload State File to Azure Storage
      run: |
        az storage blob upload \\
          --account-name "<ストレージアカウント名>" \\
          --container-name "<コンテナ名>" \\
          --file terraform/terraform.tfstate \\
          --name "${{ github.event.inputs.resource_group_name }}.tfstate"

私が遭遇したエラー

【terraform initコマンド実行時に出たエラー】

  • main.tfファイルのresource以降の項目のダブルクォーテーションを付け忘れている場合に形式エラー発生
resource "azurerm_api_management" "common" {
        name                = 
"rag-apim-${var.client}-${var.environment}"
〜〜〜〜〜以下略〜〜〜〜〜

【terraform applyコマンド実行時に出たエラー】

  • LinuxOSのAzure Functionsを立ち上げたい場合、App Service Planのreserved項目がfalseになっていると設定の不整合エラー発生
    • Linux OSのAzure Functionsを立ち上げたい場合、App Service Planのreservedパラメータをtrueに設定する必要があります。これがfalseのままだとOSタイプとの整合性が取れず、デプロイ時に設定不整合のエラーが発生します。
resource "azurerm_app_service_plan" "insertdb-function" {
        name                = "rag-appservice-${var.client}-${var.environment}"
        location            = data.azurerm_resource_group.rg.location
        resource_group_name = data.azurerm_resource_group.rg.name
        kind                = "Linux"
        reserved            = true
        sku {
                tier = "Standard"
                size = "S1"
        }
}
  • Azure Application Insightsでoutput項目を記載している場合に重要情報出力エラー発生
output "instrumentation_key" {
  value = azurerm_application_insights.example.instrumentation_key
}

動作確認

GitHubリポジトリのActionsからサイドバーにある作成したActionsを選択してRun workflowをクリックします。必要事項を入力して緑のRun workflowをクリックするとActionsが動き出します。

workflow runsから任意の動作結果を選択するとログを確認できます。

まとめ

今回はGitHub ActionsとTerraformを用いたAzure Functionsの作成自動化をご紹介しました。

何度も同じシステムを構築しないといけない場合はかなり業務効率化になると思います。

次回はAzure Functionsのコードデプロイもご紹介できればと考えているのでそちらも参考にしていただけると幸いです。

この記事が役に立ったと思ったら、
ぜひ「いいね」とシェアをお願いします!

リンクをコピーXでシェアするfacebookでシェアする

この記事を書いた人

生成AI基盤チーム
生成AI基盤チーム
メンバーズ社内の部署横断で、生成AI基盤の設計・構築、生成AI活用支援、ナレッジ共有などを行っているR&Dチームです。現在は、DevOps支援を専門とするデブオプスリードカンパニーのメンバーを中心とした構成になっています。DevOpsやSRE、インフラなどの観点を中心に、さまざまなナレッジを積極的に発信していきます!
詳しく見る
ページトップへ戻る