GCP - Pub/Sub サービス概要

GCPのPub/Subサービスの概要をまとめました。

# 概要
メッセージの受け渡しと非同期統合を実装するサービス。
同様のサービスに`Cloud Tasks`があるがそれぞれ異なる一連のユースケースを対象に設計されています。 
どちらを選択するかは「[Cloud Tasks か Pub/Sub かの選択](https://cloud.google.com/pubsub/docs/tasks-vs-pubsub?hl=ja)」を参考に各自のユースケースに照らし合わせて選択するとよい。

# [pull 配信または push 配](https://cloud.google.com/pubsub/docs/subscriber?hl=ja)
サブスクリプションでは、メッセージ配信に push または pull のメカニズムを使用できます。このメカニズムは、いつでも変更または構成できます。
### [pull サブスクリプション](https://cloud.google.com/pubsub/docs/subscriber?hl=ja#pull-subscription)
pull 配信では、サブスクライバー アプリケーションが Pub/Sub サーバーに対してリクエストを開始し、メッセージを取得します。

1. サブスクライブしているアプリケーションは、メッセージの配信をリクエストする pull メソッドを明示的に呼び出します。
2. Pub/Sub サーバーはメッセージ（キューが空の場合はエラー）と確認応答 ID で応答します。
3. サブスクライバーは戻された確認応答 ID を使用して acknowledge メソッドを明示的に呼び出し、受信を確認します。

### [push サブスクリプション](https://cloud.google.com/pubsub/docs/subscriber?hl=ja#push-subscription)
push 配信[^1]では、Pub/Sub がサブスクライバー アプリケーションに対してリクエストを開始し、メッセージを配信します。
[^1]: 注: VPC Service Controls で保護されているプロジェクトでは、push サブスクリプションを新規作成できません。既存の push サブスクリプションは引き続き機能しますが、VPC Service Controls による保護は受けられません。

1. Pub/Sub サーバーは、事前構成されたエンドポイントで、サブスクライバー アプリケーションに各メッセージを HTTPS リクエストとして送信します。
2. エンドポイントは、HTTP 成功ステータス コードを返すことでメッセージに確認応答します。不成功のレスポンスは、メッセージを再送信する必要があることを示します。

#### pull配信とpush配信の選択のガイドライン
##### pull配信

- メッセージが多い場合（1 秒間に 1 個超）
- メッセージ処理の効率とスループットが重要
- 非自己署名 SSL 証明書を持つパブリック HTTPS エンドポイントの設定が現実的ではない場合

##### push配信

- 同じ webhook で処理する必要がある複数のトピック
- App Engine スタンダードおよび Cloud Functions のサブスクライバー
- Google Cloud の依存関係（認証情報やクライアント ライブラリ）が設定できない環境

# [Cloud Pub/Sub 認証](https://cloud.google.com/pubsub/docs/authentication?hl=ja)
### サービス アカウント
ローカルの開発でも本番環境のアプリケーションでも、ほとんどのユースケースでサービス アカウントの使用が推奨されている。
`App Engine`から利用するのであれば`App Engine`のサービスアカウントが利用されるので適切なロールを紐づけてあげる必要がある。

#### 認証情報の自動検出  
 Google Cloud クライアント ライブラリでは、アプリケーションのデフォルト認証情報（ADC）を使用してアプリケーションの認証情報を検出する
##### デフォルト認証情報（ADC）
1. 環境変数`GOOGLE_APPLICATION_CREDENTIALS` の設定値で確認
2. サービスで実行されているアプリケーションに応じて、`Compute Engine`、`Google Kubernetes Engine`、`Cloud Run`、`App Engine`、`Cloud Functions` によって提供されているデフォルトのサービス アカウントを使用

##### App Engine スタンダード環境での認証情報の取得
アプリケーションが App Engine スタンダード環境で実行されている場合、`App Engine App Identity API` を使用して認証情報を取得できる。

# [アクセス制御](https://cloud.google.com/pubsub/docs/access-control?hl=ja)
Pub/Sub では、アクセス制御に `Cloud Identity and Access Management（Cloud IAM）`を使用する。

Pub/Sub では、アクセス制御をプロジェクト レベルと個々のリソースレベルで構成できます。
以下例：

- Cloud プロジェクト全体ではなく、トピックごと、またはサブスクリプションごとにアクセス権を付与します。
- 機能が限定されたアクセス権を付与します。たとえば、トピックにメッセージをパブリッシュするのみのアクセス権、サブスクリプションからのメッセージを使用するのみで、トピックやサブスクリプションを削除できないアクセス権などです。 
- プロジェクト内のすべての Pub/Sub リソースに対するアクセス権限を、デベロッパーのグループに付与します。

#### [権限と役割](https://cloud.google.com/pubsub/docs/access-control?hl=ja#permissions_and_roles)

##### ロール


| ロール       |権限 |    リソールタイプ    |
|:-----------------|:------------------|:------------------|
| roles/pubsub.publisher| pubsub.topics.publish |トピック|
| roles/pubsub.subscriber | pubsub.snapshots.seek | スナップショット |
|              | pubsub.subscriptions.consume | サブスクリプション        |
|               | pubsub.topics.attachSubscription |         トピック         |
| roles/pubsub.viewer またはroles/viewer| 上記すべてと下記の権限 |       |
|          | pubsub.snapshots.get | スナップショット       |
|          |   pubsub.snapshots.list |      プロジェクト       |
|          |   pubsub.subscriptions.get |      サブスクリプション       |
|          |   pubsub.subscriptions.list |      プロジェクト       |
|          |   pubsub.topics.get |      トピック       |
|          |   pubsub.topics.list |      プロジェクト       |
|          |   resourcemanager.projects.get |      プロジェクト       |
|          |   servicemanagement.projectSettings.get |      プロジェクト       |
|          |   serviceusage.quotas.get |      プロジェクト       |
|          |   serviceusage.services.get |      プロジェクト       |
|          |   serviceusage.services.list |      プロジェクト       |
| roles/pubsub.editor またはroles/editor| 上記すべてと下記の権限 |       |
|　　　　　　| スナップショットのCRUDに関する権限 |  スナップショット     |
|　　　　　　| サブスクリプションのCRUDに関する権限 |   サブスクリプション    |
|　　　　　　| トピックのCRUDに関する権限|    トピック   |
| roles/pubsub.admin またはroles/owner| 上記すべてと下記の権限 |       |
|　　　　　　| スナップショットのiamポリシーの取得と設定権限 |  スナップショット     |
|　　　　　　| サブスクリプションのiamポリシーの取得と設定権限 |   サブスクリプション    |
|　　　　　　| トピックののiamポリシーの取得と設定権限|    トピック   |

#### [ポリシーについて](https://cloud.google.com/iam/docs/policies?hl=ja)
Google Cloud リソースのアクセス制御は、Cloud IAM ポリシーによって管理されます。Cloud IAM ポリシーはリソースに関連付けられます。
このポリシーは、そのリソース自体へのアクセスだけでなく、ポリシーの継承により、子リソースへのアクセスも管理します。
##### ポリシーの構造
ポリシーは、バインディング、監査構成、メタデータから構成される。

- **バインディング** : リソースへのアクセス権を付与する方法を指定し1 つ以上のメンバーを 1 つのロールに関連付けて（バインディング）、コンテキスト固有の条件を適用
- **監査構成** : `AuditConfig フィールド`には、アクセス監査の構成データを指定
- **メタデータ** : メタデータには、Etag やバージョンなど、ポリシーに関する追加情報を記述

##### バインディングのリスト
各バインディングは次のフィールドで構成されます。

- メンバー : ID またはプリンシパルともいいます。ユーザー アカウント、サービス アカウント、Google グループ、ドメインなどがメンバーになります。
- ロール : Google Cloud リソースに対してアクションを実行するための権限をまとめたものです。  
- 条件 : リクエストの属性（リクエストの送信元、ターゲット リソースなど）に基づいてロールのバインディングをさらに制限する論理式です。条件は通常、アクセスがリクエストされたときのコンテキストに応じて制限を行う場合に使用します。

##### AuditConfig フィールド
ポリシーの監査ロギングを構成するときに使用します。

##### メタデータ
次のフィールドがあります。

- etag フィールド : 同時制御に使用し、ポリシーが一貫した方法で更新されるようにします。
- version フィールド : 特定のポリシーのスキーマ バージョンを指定します。

**[例: 条件付きロール バインディングを含むポリシー](https://cloud.google.com/iam/docs/policies?hl=ja#conditional-bindings)**

```
{
      "bindings": [
        {
          "members": [
            "group:prod-dev@example.com",
            "serviceAccount:prod-dev-example@appspot.gserviceaccount.com"
          ],
          "role": "roles/appengine.Deployer",
          "condition": {
              "title": "Expires_July_1_2020",
              "description": "Expires on July 1, 2020",
              "expression":
                "request.time < timestamp('2020-07-01T00:00:00.000Z')"
          }
        }
      ],
      "etag": "BwWKmjvelug=",
      "version": 3
    }
```

# [トピックとサブスクリプションの管理](https://cloud.google.com/pubsub/docs/admin?hl=ja)
Pub/Sub のトピックとサブスクリプションを作成、削除、管理
### トピックの管理
#### [トピックの作成](https://cloud.google.com/pubsub/docs/admin?hl=ja#creating_a_topic)
**CLI**

```
gcloud pubsub topics create myTopic
```
**Python**

```topic.py
from google.cloud import pubsub_v1

    # TODO project_id = "Your Google Cloud Project ID"
    # TODO topic_name = "Your Pub/Sub topic name"

    publisher = pubsub_v1.PublisherClient()
    topic_path = publisher.topic_path(project_id, topic_name)

    topic = publisher.create_topic(topic_path)

    print("Topic created: {}".format(topic))
```

#### [トピックの削除](https://cloud.google.com/pubsub/docs/admin?hl=ja#deleting_a_topic)
トピックを削除しても、サブスクリプションは削除されません。サブスクライバーは、サブスクリプションのメッセージ バックログを使用できます。トピックが削除されると、そのサブスクリプションのトピック名は _deleted-topic_ になります。トピックを削除した直後に同名のトピックを新規作成しようとすると、エラーが発生します。
**CLI**

```
gcloud pubsub topics delete myTopic
```

**Python**

```topic.py
from google.cloud import pubsub_v1

    # TODO project_id = "Your Google Cloud Project ID"
    # TODO topic_name = "Your Pub/Sub topic name"

    publisher = pubsub_v1.PublisherClient()
    topic_path = publisher.topic_path(project_id, topic_name)

    publisher.delete_topic(topic_path)

    print("Topic deleted: {}".format(topic_path))
```

#### [トピックの一覧表示](https://cloud.google.com/pubsub/docs/admin?hl=ja#listing_topics)
デフォルトでは、クエリあたり最大で 100 件の結果が返されます。 ページサイズ パラメータを使用すると、最大で 1,000 までの代替値を指定できます。
**CLI**

```
gcloud pubsub topics list
```
**Python**

```topic.py
from google.cloud import pubsub_v1

    # TODO project_id = "Your Google Cloud Project ID"

    publisher = pubsub_v1.PublisherClient()
    project_path = publisher.project_path(project_id)

    for topic in publisher.list_topics(project_path):
        print(topic)
```

### サブスクリプションの管理
#### [サブスクリプションの作成](https://cloud.google.com/pubsub/docs/admin?hl=ja#creating_subscriptions)
デフォルトでは、サブスクリプションは **pull 配信**を使用します。
**CLI**

```
gcloud pubsub subscriptions create SUBSCRIPTION_ID
      --topic=TOPIC_ID
      [--ack-deadline=ACK_DEADLINE]
      [--message-retention-duration=MESSAGE_RETENTION_DURATION]
      [--expiration-period=EXPIRATION_PERIOD]
      [--push-endpoint=PUSH_ENDPOINT]
```
**Python**


```subscription.py
from google.cloud import pubsub_v1

    # TODO project_id = "Your Google Cloud Project ID"
    # TODO topic_name = "Your Pub/Sub topic name"
    # TODO subscription_name = "Your Pub/Sub subscription name"

    subscriber = pubsub_v1.SubscriberClient()
    topic_path = subscriber.topic_path(project_id, topic_name)
    subscription_path = subscriber.subscription_path(
        project_id, subscription_name
    )

    subscription = subscriber.create_subscription(
        subscription_path, topic_path
    )

    print("Subscription created: {}".format(subscription))

    subscriber.close()
```
次のサンプルは、**push 配信**でサブスクリプションを作成する方法を示しています。

```subscription.py
from google.cloud import pubsub_v1

    # TODO project_id = "Your Google Cloud Project ID"
    # TODO topic_name = "Your Pub/Sub topic name"
    # TODO subscription_name = "Your Pub/Sub subscription name"
    # TODO endpoint = "https://my-test-project.appspot.com/push"

    subscriber = pubsub_v1.SubscriberClient()
    topic_path = subscriber.topic_path(project_id, topic_name)
    subscription_path = subscriber.subscription_path(
        project_id, subscription_name
    )

    push_config = pubsub_v1.types.PushConfig(push_endpoint=endpoint)

    subscription = subscriber.create_subscription(
        subscription_path, topic_path, push_config
    )

    print("Push subscription created: {}".format(subscription))
    print("Endpoint for subscription is: {}".format(endpoint))

    subscriber.close()
```
#### [サブスクリプション プロパティの使用](https://cloud.google.com/pubsub/docs/admin?hl=ja#using_subscription_properties)
サブスクリプション プロパティは、サブスクリプションを作成または更新するときに設定できます。

#### [配信方法の変更](https://cloud.google.com/pubsub/docs/admin?hl=ja#change_pull_push)
Cloud Console、gcloud コマンドライン ツール、Cloud Pub/Sub API のいずれかを使用して、push サブスクリプションと pull サブスクリプションを切り替えることができます。

サブスクリプションで pull 配信がすでに使用されている場合、push エンドポイントを設定すると配信方法がプッシュ push 配信に切り替わります。
push エンドポイントを空の文字列に変更すると、push 配信から pull 配信に切り替えることができます。
**CLI**

```
gcloud pubsub subscriptions modify-push-config SUBSCRIPTION_ID
  --push-endpoint=PUSH_ENDPOINT
    
```
**Python**

```subscription.py
from google.cloud import pubsub_v1

    # TODO project_id = "Your Google Cloud Project ID"
    # TODO topic_name = "Your Pub/Sub topic name"
    # TODO subscription_name = "Your Pub/Sub subscription name"
    # TODO endpoint = "https://my-test-project.appspot.com/push"

    subscriber = pubsub_v1.SubscriberClient()
    subscription_path = subscriber.subscription_path(
        project_id, subscription_name
    )

    push_config = pubsub_v1.types.PushConfig(push_endpoint=endpoint)

    subscription = pubsub_v1.types.Subscription(
        name=subscription_path, push_config=push_config
    )

    update_mask = {"paths": {"push_config"}}
    subscriber.update_subscription(subscription, update_mask)
    result = subscriber.get_subscription(subscription_path)

    print("Subscription updated: {}".format(subscription_path))
    print("New endpoint for subscription is: {}".format(result.push_config))

    subscriber.close()
```

#### [サブスクリプションの一覧表示](https://cloud.google.com/pubsub/docs/admin?hl=ja#listing_subscriptions)
**CLI**

```
# Project > Subscription list
gcloud pubsub subscriptions list [--project=PROJECT_ID]

# Project > Topic > Subscription list
gcloud pubsub topics list-subscriptions TOPIC_ID 
```
**Python : Project > Subscription list**

```subscription.py
from google.cloud import pubsub_v1

    # TODO project_id = "Your Google Cloud Project ID"

    subscriber = pubsub_v1.SubscriberClient()
    project_path = subscriber.project_path(project_id)

    for subscription in subscriber.list_subscriptions(project_path):
        print(subscription.name)

    subscriber.close()
```

**Python : Project > Topic > Subscription list**

```subscription.py
from google.cloud import pubsub_v1

    # TODO project_id = "Your Google Cloud Project ID"
    # TODO topic_name = "Your Pub/Sub topic name"

    publisher = pubsub_v1.PublisherClient()
    topic_path = publisher.topic_path(project_id, topic_name)

    for subscription in publisher.list_topic_subscriptions(topic_path):
        print(subscription)
```

#### [サブスクリプションを削除する](https://cloud.google.com/pubsub/docs/admin?hl=ja#delete_subscription)
**CLI**

```
gcloud pubsub subscriptions delete SUBSCRIPTION_ID
```
**Python**

```subscription.py
from google.cloud import pubsub_v1

    # TODO project_id = "Your Google Cloud Project ID"
    # TODO subscription_name = "Your Pub/Sub subscription name"

    subscriber = pubsub_v1.SubscriberClient()
    subscription_path = subscriber.subscription_path(
        project_id, subscription_name
    )

    subscriber.delete_subscription(subscription_path)

    print("Subscription deleted: {}".format(subscription_path))

    subscriber.close()
```

### [リソース名](https://cloud.google.com/pubsub/docs/admin?hl=ja#resource_names)
Pub/Sub リソース名は、サブスクリプションやトピックなどの Pub/Sub のリソースを一意に識別します。

[GCP - Pub/Sub サービス構築チュートリアル](https://qiita.com/minarai/private/a642589ffacc28855a13)へ続きます