内部 Cloud Run サービスと公共のインターネットの両方にアクセスするように Cloud Run サービスを構成する

1. はじめに

概要

多くの組織は、サービスやアプリケーションのネットワーク トラフィックを保護するために、境界制御を備えた Google Cloud 上で Virtual Private Cloud(VCP)ネットワークを使用して、データの引き出しを防止しています。VPC ネットワークは、Google の本番環境ネットワーク内に仮想的に実装された物理ネットワークです。VPC ネットワークは Compute Engine 仮想マシン(VM)インスタンスの接続を提供し、内部アプリケーション ロードバランサにネイティブの内部パススルー ネットワーク ロードバランサとプロキシ システムを提供します。また、Cloud VPN トンネルと Cloud Interconnect 用の VLAN アタッチメントを使用してオンプレミス ネットワークに接続し、Google Cloud の外部ロードバランサからバックエンドにトラフィックを分散します。

VM とは異なり、Cloud Run サービスは、デフォルトでは特定の VPC ネットワークに関連付けられていません。この Codelab では、VPC からのトラフィックのみが Cloud Run サービス(バックエンド サービスなど)にアクセスできるように、上り(内向き)接続の設定を変更する方法を示します。さらに、この Codelab では、VPC を介して 2 つ目のサービス(フロントエンド サービスなど)からバックエンドの Cloud Run サービスの両方にアクセスし、引き続き公共のインターネット アクセスを維持する方法について説明します。

この例では、バックエンドの Cloud Run サービスは Hello World を返します。フロントエンドの Cloud Run サービスには、URL を収集するための入力フィールドが UI に用意されています。次に、フロントエンド サービスがその URL(バックエンド サービスなど)に対して GET リクエストを行うため、これが(ブラウザ間リクエストではなく)サービス間リクエストになります。フロントエンド サービスがバックエンドに到達できると、ブラウザに「hello world」というメッセージが表示されます。次に、https://curlmyip.org を呼び出してフロントエンド サービスの IP アドレスを取得する方法を紹介します。

学習内容

  • VPC から Cloud Run サービスへのトラフィックのみを許可する方法
  • フロントエンド サービスのパブリック インターネット アクセスを維持しながら、内部接続の上り(内向き)のみの Cloud Run サービス(バックエンドなど)と通信するように Cloud Run サービス(フロントエンドなど)で下り(外向き)を構成する方法。

2. 設定と要件

前提条件

Cloud Shell をアクティブにする

  1. Cloud Console で、[Cloud Shell をアクティブにする] d1264ca30785e435.png をクリックします。

cb81e7c8e34bc8d.png

Cloud Shell を初めて起動する場合は、内容を説明する中間画面が表示されます。中間画面が表示されたら、[続行] をクリックします。

d95252b003979716.png

Cloud Shell のプロビジョニングと接続に少し時間がかかる程度です。

7833d5e1c5d18f54.png

この仮想マシンには、必要なすべての開発ツールが読み込まれます。5 GB の永続的なホーム ディレクトリが用意されており、Google Cloud で稼働するため、ネットワークのパフォーマンスと認証が大幅に向上しています。この Codelab での作業のほとんどはブラウザを使って行うことができます。

Cloud Shell に接続すると、認証が完了し、プロジェクトに各自のプロジェクト ID が設定されていることがわかります。

  1. Cloud Shell で次のコマンドを実行して、認証されたことを確認します。
gcloud auth list

コマンド出力

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Cloud Shell で次のコマンドを実行して、gcloud コマンドがプロジェクトを認識していることを確認します。
gcloud config list project

コマンド出力

[core]
project = <PROJECT_ID>

上記のようになっていない場合は、次のコマンドで設定できます。

gcloud config set project <PROJECT_ID>

コマンド出力

Updated property [core/project].

3. Cloud Run サービスを作成する

環境変数を設定する

この Codelab 全体で使用する環境変数を設定できます。

PROJECT_ID=<YOUR_PROJECT_ID>
REGION=<YOUR_REGION, e.g. us-central1>
FRONTEND=frontend-with-internet
BACKEND=backend
SUBNET_NAME=default

バックエンドの Cloud Run サービスを作成する

まず、ソースコード用のディレクトリを作成し、そのディレクトリに移動します。

mkdir -p egress-private-codelab/frontend-w-internet egress-private-codelab/backend && cd egress-private-codelab/backend

次に、次の内容の `package.json` ファイルを作成します。

{
    "name": "backend-service",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "start": "node index.js"
    },
    "dependencies": {
        "express": "^4.18.1"
    }
}

次に、以下の内容で index.js ソースファイルを作成します。このファイルには、サービスのエントリ ポイントと、アプリのメインロジックが含まれています。

const express = require('express');

const app = express();

app.use(express.urlencoded({ extended: true }));

app.get('/', function (req, res) {
    res.send("hello world");
});

const port = parseInt(process.env.PORT) || 8080;
app.listen(port, () => {
    console.log(`helloworld: listening on port ${port}`);
});

最後に、次のコマンドを実行して Cloud Run サービスをデプロイします。

gcloud run deploy $BACKEND --source . --allow-unauthenticated --region $REGION

フロントエンドの Cloud Run サービスを作成する

フロントエンド ディレクトリに移動する

cd ../frontend-w-internet

次に、次の内容の package.json ファイルを作成します。

{
  "name": "frontend",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.6.6",
    "express": "^4.18.2",
    "htmx.org": "^1.9.10"
  }
}

次に、以下の内容で index.js ソースファイルを作成します。このファイルには、サービスのエントリ ポイントと、アプリのメインロジックが含まれています。

const express = require("express");
const app = express();
const port = 8080;
const path = require('path');
const axios = require('axios');

// serve static content (index.html) using
// built-in middleware function in Express 
app.use(express.static('public'));
app.use(express.urlencoded({ extended: true }));

// this endpoint receives a URL in the post body
// and then makes a get request to that URL
// results are sent back to the caller
app.post('/callService', async (req, res) => {

    const url = req.body.url;
    let message = "";

    try {
        console.log("url: ", url);
        const response = await axios.get(url);
        message = response.data;

    } catch (error) {
        message = error.message;
        console.error(error.message);
    }

    res.send(`
        ${message}
        <p>
        </p>
    `);
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
});

index.html ファイル用のパブリック ディレクトリを作成する

mkdir public
touch public/index.html

さらに、index.html を更新して以下を含めます。

<html>
  <script
    src="https://unpkg.com/htmx.org@1.9.10"
    integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
    crossorigin="anonymous"
  ></script>
  <body>
    <div style="margin-top: 100px; margin-left: 100px">
      <h1>I'm the Request Tester service on the Internet</h1>
      <form hx-trigger="submit" hx-post="/callService" hx-target="#zen">
        <label for="url"> URL:</label>
        <input
          style="width: 308px"
          type="text"
          id="url"
          name="url"
          placeholder="The backend service URL"
          required
        />
        <button hx-indicator="#loading" type="submit">Submit</button>
        <p></p>
        <span class="htmx-indicator" id="loading"> Loading... </span>
        <div id="zen" style="white-space: pre-wrap"></div>
        <p></p>
      </form>
    </div>
  </body>
</html>

最後に、次のコマンドを実行して Cloud Run サービスをデプロイします。

gcloud run deploy $FRONTEND --source . --allow-unauthenticated --region $REGION

バックエンド サービスを呼び出す

このセクションでは、2 つの Cloud Run サービスが正常にデプロイされたことを確認します。

ウェブブラウザでフロントエンド サービスの URL を開きます(例:https://frontend-your-hash-uc.a.run.app/

テキスト ボックスにバックエンド サービスの URL を入力します。このリクエストは、ブラウザからではなく、フロントエンドの Cloud Run インスタンスからバックエンドの Cloud Run サービスに転送されます。

「hello world」と表示される

4. 内部 Ingress 専用にバックエンド サービスを設定する

次の gcloud コマンドを実行して、Cloud Run サービスをプライベート ネットワークに組み込むことができます。

gcloud run services update $BACKEND --ingress internal --region $REGION

フロントエンド サービスからバックエンド サービスを呼び出そうとすると、404 エラーが発生します。フロントエンドの Cloud Run サービスの送信接続(下り、外向き)は最初にインターネットに送信されるため、Google Cloud はリクエストの送信元を認識できません。

5. VPC にアクセスするようにフロントエンド サービスを構成する

このセクションでは、VPC 経由でバックエンド サービスと通信するようにフロントエンドの Cloud Run サービスを構成します。

これを行うには、フロントエンドの Cloud Run サービスに直接 VPC 下り(外向き)を追加して、VPC ネットワーク上の内部 IP アドレスに到達できるようにする必要があります。次に、プライベート IP へのリクエストのみが VPC にルーティングされるように下り(外向き)を構成します。この構成により、引き続きフロントエンドから公共のインターネットに接続できます。詳細については、他の Cloud Run サービスからのリクエストの受信のドキュメントをご覧ください。

ダイレクト VPC 下り(外向き)を構成する

まず、次のコマンドを実行して、フロントエンド サービスでダイレクト VPC 下り(外向き)を使用します。

gcloud beta run services update $FRONTEND \
--network=$SUBNET_NAME \
--subnet=$SUBNET_NAME  \
--vpc-egress=private-ranges-only \
--region=$REGION

これで、フロントエンド サービスが VPC にアクセスできることを確認できます。

gcloud beta run services describe $FRONTEND \
--region=$REGION

次のような出力が表示されます。

VPC access:
    Network:        default
    Subnet:          default
    Egress:          private-ranges-only

限定公開の Google アクセスを有効にする

次に、次のコマンドを実行して、サブネットで限定公開の Google アクセスを有効にします。

gcloud compute networks subnets update $SUBNET_NAME \
--region=$REGION \
--enable-private-ip-google-access

次のコマンドを実行して、限定公開の Google アクセスが有効になっていることを確認できます。

gcloud compute networks subnets describe $SUBNET_NAME \
--region=$REGION \
--format="get(privateIpGoogleAccess)"

run.app URL の Cloud DNS ゾーンを作成する

最後に、run.app URL の Cloud DNS ゾーンを作成して、Google Cloud がこれらの URL を内部 IP アドレスとして扱えるようにします。

前のステップで、プライベート範囲のみへのダイレクト VPC 下り(外向き)を構成しました。つまり、フロントエンド サービスからのアウトバウンド接続は、宛先が内部 IP の場合にのみ VPC ネットワークに送信されます。ただし、バックエンド サービスは、パブリック IP に解決される run.app URL を使用します。

このステップでは、run.app URL の Cloud DNS ゾーンを作成して、private.googleapis.com の IP アドレス範囲に解決します。この IP アドレス範囲は内部 IP アドレスとして認識されます。これで、これらの範囲へのリクエストはすべて VPC ネットワーク経由でルーティングされるようになります。

これは、https://cloud.google.com/run/docs/securing/private-networking#from-other-services で行うことができます。

# do not include the https:// in your DNS Name
# for example: backend-<hash>-uc.a.run.app
DNS_NAME=<your backend service URL without the https://>

gcloud dns --project=$PROJECT_ID managed-zones create codelab-backend-service \
 --description="" \
 --dns-name="a.run.app." \
 --visibility="private" \
 --networks=$SUBNET_NAME

gcloud dns --project=$PROJECT_ID record-sets create $DNS_NAME. \
--zone="codelab-backend-service" \
 --type="A" \
 --ttl="60" \
--rrdatas="199.36.153.8,199.36.153.9,199.36.153.10,199.36.153.11"

これで、ウェブサイトのバックエンド サービスにアクセスしようとすると、「hello world」と表示されます。返されます。

https://curlmyip.org/ からインターネットに接続しようとすると、IP アドレスが表示されます。

6. トラブルシューティング

設定が正しく行われていない場合に、次のようなエラー メッセージが表示されることがあります。

  • エラー getaddrinfo ENOTFOUND backend-your-hash-uc.a.run.app が表示された場合は、「https://」を追加していないことを確認します。DNS の A レコードにマッピングします。
  • ゾーンの構成後にバックエンドにアクセスしようとしたときに 404 エラーが発生した場合は、グローバル run.app レコードのキャッシュが期限切れになるまで待つか(6 時間など)、新しいリビジョンを作成する(キャッシュをクリアするには)次のコマンドを実行します。gcloud beta run services update $FRONTEND --network=$SUBNET_NAME --subnet=$SUBNET_NAME --vpc-egress=private-ranges-only --region=$REGION

7. 完了

これでこの Codelab は完了です。

Cloud Run でのプライベート ネットワーキングに関するドキュメントを確認することをおすすめします。

学習した内容

  • VPC から Cloud Run サービスへのトラフィックのみを許可する方法
  • フロントエンド サービスのパブリック インターネット アクセスを維持しながら、内部接続の上り(内向き)のみの Cloud Run サービス(バックエンドなど)と通信するように Cloud Run サービス(フロントエンドなど)で下り(外向き)を構成する方法。

8. クリーンアップ

不注意による料金の発生(たとえば、この Cloud Run サービスが誤って無料枠の毎月の Cloud Run 呼び出し割り当てよりも多く呼び出された場合)を回避するには、Cloud Run サービスを削除するか、手順 2 で作成したプロジェクトを削除します。

Cloud Run サービスを削除するには、Cloud Run Cloud コンソール(https://console.cloud.google.com/functions/)に移動し、この Codelab で作成した $FRONTEND サービスと $BACKEND サービスを削除します。

プロジェクト全体を削除する場合は、https://console.cloud.google.com/cloud-resource-manager に移動し、手順 2 で作成したプロジェクトを選択して [削除] を選択します。プロジェクトを削除する場合は、Cloud SDK でプロジェクトを変更する必要があります。使用可能なすべてのプロジェクトのリストを表示するには、gcloud projects list を実行します。