1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

n8nをCloud RunにDeployしてIAPで内部アクセス許可する (Terraform)

Last updated at Posted at 2025-05-18

はじめに

  • n8nは社内のLLM活用を助ける強力なノーコードツール
  • Cloud RunはGCPのマネージドコンテナサービス
  • IAPは、Googleアカウントでアクセス管理簡単に実現できる

今回は「n8nをCloud Runでミニマムに動かす」というのをテーマに紹介します。

  • 無料枠がたくさんあるCloud Run
  • GCSをデータの永続化 (Cloud SQLのDB代も使わずに済む)
  • Cloud RunのIAPがPre-GAになったので、アクセス管理が簡単に設定できます。Pre-GAなので、個人や社内利用くらいまでが良さそうです。

Terraformの設定

Cloud Storage

GCSのBucket作成

resource "google_storage_bucket" "n8n_data" {
  name          = "naka-test-n8n-data"
  location      = var.region
  force_destroy = false

  uniform_bucket_level_access = true
}

Service Account

Service Accountの作成と上で作成したBucketへの権限を付与

# service account for cloud run
resource "google_service_account" "n8n" {
  account_id   = "n8n-service" # need to be between 6-30 characters long
  display_name = "n8n"
}

# access to bucket
resource "google_storage_bucket_iam_member" "n8n_gcs_access" {
  bucket = google_storage_bucket.n8n_data.name
  role   = "roles/storage.objectUser"
  member = google_service_account.n8n.member
}

Cloud Run service

Cloud Run serviceとIAPの設定

# n8n Cloud Run service
resource "google_cloud_run_v2_service" "n8n" {
  provider     = google-beta
  name         = "n8n"
  location     = var.region
  launch_stage = "BETA" // IAP is Pre-GA
  iap_enabled  = true   // enable IAP

  template {
    containers {
      image = "n8nio/n8n:1.93.0"

      ports {
        container_port = 5678
      }

      resources {
        limits = {
          cpu    = "1"
          memory = "512Mi"
        }
      }

      env {
        name  = "DB_TYPE"
        value = "sqlite"
      }

      env {
        name  = "N8N_USER_FOLDER"
        value = "/home/node/.n8n"
      }

      env {
        name = "N8N_WEBHOOK_URL"
        value = "<cloud run のendpoint>"
      }

      env {
        name = "N8N_PROTOCOL"
        value = "HTTPS"
      }

      volume_mounts {
        name       = "n8n-data"
        mount_path = "/home/node/.n8n"
      }
    }

    service_account = google_service_account.n8n.email

    # gcs volume
    volumes {
      name = "n8n-data"
      gcs {
        bucket = google_storage_bucket.n8n_data.name
      }
    }

    scaling {
      min_instance_count = 1
      max_instance_count = 1
    }
  }
}

min_instance_countを1にしてスケジュールなどのBackgroundタスクも実行できるようにします。
スケジュールなど使わない場合には、min_instance_countを0にして、アクセスがない場合にはSleepさせてコストを抑えることも可能です

max_instance_countを1にして、複数インスタンスから同一ファイルへの書き込みロックはないので一つのインスタンスのみにしています。ここらへんが気になる場合は、DB_TYPEをpostgresdbなどのDatabaseにしたほうが良いでしょう。

Cloud Storage FUSE では、同じファイルへの複数書き込みの同時実行制御(ファイルのロック)は行いません。複数の書き込みによってファイルの置き換えが試みられた場合は、最後の書き込みが有効となり、それより前の書き込みはすべて失われます。 (ref)

IAPを使うには現時点では、google-beta providerの v6.30.0 以上が必要です。

IAPの権限付与

locals {
  n8n_iap_access_members = [
    "user:your@example.com",
  ]
}
resource "google_iap_web_cloud_run_service_iam_member" "n8n_iap_access" {
  for_each               = toset(local.n8n_iap_access_members)
  project                = google_cloud_run_v2_service.n8n.project
  location               = google_cloud_run_v2_service.n8n.location
  cloud_run_service_name = google_cloud_run_v2_service.n8n.name
  role                   = "roles/iap.httpsResourceAccessor"
  member                 = each.value
}

google providerの v6.31.0 以上が必要です。

Service AccountからIAPの後ろにn8nを呼び出す

今回は n8nのWebhookのエンドポイントを叩くことにします。(ref: https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/)

GCPの公式ドキュメントは、こちらです。

まず叩くService AccountとCloud RunのEndpointを指定します。Cloud Schedulerから叩く予定であれば、n8n-scheduler@.iam.gserviceaccount.com など Cloud Runで使っているservice accountとは別のservice accountを指定する事ができます。

SERVICE_ACCOUNT=xxx@<project>.iam.gserviceaccount.com
URL=$(gcloud run services describe n8n --project <project> --region asia-northeast1 --format json | jq -r '.status.url')

次に、jwtを生成するためのclaimを作成します。

cat > claim.json << EOM
{
  "iss": "$SERVICE_ACCOUNT",
  "sub": "$SERVICE_ACCOUNT",
  "aud": "$URL/webhook-test/test",
  "iat": $(date +%s),
  "exp": $((`date +%s` + 3600))
}
EOM

audには、$URL/webhook-test/test を設定していますが、n8n上でwebhookを設定したときのtest用のエンドポイントです。本番は、$URL/webhook-test/test$URL/webhook/testになります。/testの部分は自分で好きなpathを決めることができます。

audienceの設定は、$URL のみならず、完全なパスを指定する必要があるのでご注意ください。audienceが実際に叩くPathと異なる場合は、Invalid IAP credentials: Audience specified does not match requested endpointというエラーが返ります。

サインをしてjwtを発行します。

gcloud iam service-accounts sign-jwt --iam-account="$SERVICE_ACCOUNT" claim.json output.jwt

最後にendpointをたたいて見ます

curl -X POST -H "Authorization: Bearer $(cat output.jwt)" "$URL/webhook-test/test"

{"message":"Workflow was started"} が見えたら叩かれたことを確かめられます。

Cloud SchedulerからIAPで設定したCloud Runのエンドポイントを叩く (できてない❌️)

Cloud SchedulerからCloud Runを叩く場合、IAPを設定していない場合には、以下のようにService Accountを指定することで実行ができます。

# Cloud Scheduler用のサービスアカウント
resource "google_service_account" "n8n_scheduler" {
  account_id   = "n8n-scheduler"
  display_name = "Service Account for Cloud Scheduler to invoke n8n webhook"
}

# サービスアカウントにCloud Run起動権限を付与
resource "google_cloud_run_service_iam_member" "n8n_scheduler_is_run_invoker_of_n8n" {
  project  = google_cloud_run_v2_service.n8n.project
  location = google_cloud_run_v2_service.n8n.location
  service  = google_cloud_run_v2_service.n8n.name
  role     = "roles/run.invoker"
  member   = google_service_account.n8n_scheduler.member
}

# Cloud Schedulerジョブ
resource "google_cloud_scheduler_job" "n8n_webhook_test" {
  name             = "n8n-webhook-test"
  description      = "Daily job to trigger n8n webhook at 9 AM JST"
  schedule         = "0 0 * * *" # 毎日 UTC 0:00 (JST 9:00)
  time_zone        = "Asia/Tokyo"
  attempt_deadline = "120s" # タイムアウトを120秒に設定

  http_target {
    http_method = "POST"
    uri         = "${google_cloud_run_v2_service.n8n.uri}/webhook/test"

    # IAPを使用するための設定
    oidc_token {
      service_account_email = google_service_account.n8n_scheduler.email
      audience              = google_cloud_run_v2_service.n8n.uri
    }
  }

  depends_on = [
    google_cloud_run_service_iam_member.n8n_scheduler_is_run_invoker_of_n8n,
    google_iap_web_cloud_run_service_iam_member.n8n_iap_access,
  ]
}

IAPの場合には、User同様に roles/iap.httpsResourceAccessorの権限を付与しましたが、

# サービスアカウントにIAPの権限を付与
resource "google_iap_web_cloud_run_service_iam_member" "n8n_scheduler_iap_access" {
  project                = google_cloud_run_v2_service.n8n.project
  location               = google_cloud_run_v2_service.n8n.location
  cloud_run_service_name = google_cloud_run_v2_service.n8n.name
  role                   = "roles/iap.httpsResourceAccessor"
  member                 = google_service_account.n8n_scheduler.member
}

実行できませんでした。❌️

Error

{
  "insertId": "18etbn1fgv7hvt",
  "jsonPayload": {
    "debugInfo": "URL_ERROR-ERROR_AUTHENTICATION. Original HTTP response code number = 401",
    "status": "UNAUTHENTICATED",
    "jobName": "projects/xxxx/locations/asia-northeast1/jobs/n8n-webhook-test",
    "targetType": "HTTP",
    "@type": "type.googleapis.com/google.cloud.scheduler.logging.AttemptFinished",
    "url": "https://n8n-xxx.asia-northeast1.run.app/webhook/test"
  },
  "httpRequest": {
    "status": 401
  },
  "resource": {
    "type": "cloud_scheduler_job",
    "labels": {
      "project_id": "xxxx",
      "location": "asia-northeast1",
      "job_id": "n8n-webhook-test"
    }
  },
  "timestamp": "2025-05-22T06:36:48.073228186Z",
  "severity": "ERROR",
  "logName": "projects/xxx/logs/cloudscheduler.googleapis.com%2Fexecutions",
  "receiveTimestamp": "2025-05-22T06:36:48.073228186Z"
}

これはまだ解決できてないので、どなたかご存知の方いれば教えていただきたいです :bow:

まとめ

n8nを簡単にCloud RunでDeployしてアクセス管理をIAPを用いて行える設定を紹介しました。

これによって、Cloud Runで払い出されたエンドポイント https://n8n-<projectnumber>.<region>.run.appからアクセスができるようになりました。

Cloud SQLを使うケースは、n8n on Cloud Run (ツール比較から選定まで) が参考になります。

References

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?