# Connectivity and emulator mode (/connectors/gcp-pub-sub/how-to/connectivity-and-emulator-mode)



This guide covers how a Google Cloud Pub/Sub client reaches the connector: the standard
`PUBSUB_EMULATOR_HOST` drop-in (the zero-code-change contract), the per-language emulator opt-in,
the no-auth / insecure-gRPC posture, the cosmetic `AdvertisedEndpoint` dashboard hint, the
gRPC-only constraint, and the sticky-load-balancer caveat for clusters.

## The emulator protocol — zero code changes [#the-emulator-protocol--zero-code-changes]

The connector is a dedicated **gRPC listener** (default port **8085**, the Pub/Sub emulator
convention) inside KubeMQ. Every official Pub/Sub client library (Go, Python, Java, Node.js, C#,
Ruby) and `gcloud` honour the standard `PUBSUB_EMULATOR_HOST` environment variable. When that
variable is set, the SDK **clears its Google credentials, skips Google auth, and dials insecure
gRPC** — exactly as it would against Google's own local emulator. Pointing an unmodified Pub/Sub
application at KubeMQ therefore requires **no code changes**:

```bash
export PUBSUB_EMULATOR_HOST=localhost:8085   # connector default gRPC port; SDK uses the insecure path
export PUBSUB_PROJECT_ID=my-project          # any id; the project segment is parsed but ignored
# Some clients and gcloud also read this alias:
# export GOOGLE_CLOUD_PROJECT=my-project
```

There is **no bespoke wrapper variable** — the standard Google env var *is* the contract, and that
zero-code-change drop-in is the connector's headline value proposition.

<Callout type="info">
  **Project id is parsed but ignored.** The connector validates the `projects/{p}` segment but is
  **single-tenant** (like the emulator): resource ids are global across "projects". Any project id
  works; topic `orders` is always Events Store channel `gcp.orders` regardless of project. See
  [Channel mapping](/connectors/gcp-pub-sub/reference/channel-mapping).
</Callout>

## Per-language emulator opt-in [#per-language-emulator-opt-in]

Most clients auto-detect the emulator from `PUBSUB_EMULATOR_HOST` with no extra code; two need an
explicit flag or constructor argument:

| Language | Construction (emulator)                                                                                                            | Auto-detect?             |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| Go       | `pubsub.NewClient(ctx, projectID)` — reads `PUBSUB_EMULATOR_HOST` and dials insecurely.                                            | Yes                      |
| Python   | `pubsub_v1.PublisherClient()` / `SubscriberClient()` — honours the env var.                                                        | Yes                      |
| Node/TS  | `new PubSub({ projectId })` — auto-detects the emulator from the env var.                                                          | Yes                      |
| Java     | Point a plaintext `ManagedChannel` at the emulator host with `NoCredentialsProvider` when `PUBSUB_EMULATOR_HOST` is set.           | No — explicit channel    |
| C#       | `new PublisherServiceApiClientBuilder { EmulatorDetection = EmulatorDetection.EmulatorOnly }.Build()` (or `EmulatorOrProduction`). | No — `EmulatorDetection` |
| Ruby     | `Google::Cloud::PubSub.new(project_id: ENV["PUBSUB_PROJECT_ID"], emulator_host: ENV["PUBSUB_EMULATOR_HOST"])`.                     | No — `emulator_host:`    |

The three clients that need explicit emulator wiring are Java, C#, and Ruby:

<Tabs groupId="language" items="['Java','C#','Ruby']">
  <Tab value="Java">
    ```java
    // When PUBSUB_EMULATOR_HOST is set, build a plaintext channel and clear credentials.
    String host = System.getenv("PUBSUB_EMULATOR_HOST"); // e.g. localhost:8085
    ManagedChannel channel = ManagedChannelBuilder.forTarget(host).usePlaintext().build();
    TransportChannelProvider channelProvider =
        FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel));
    CredentialsProvider credentialsProvider = NoCredentialsProvider.create();

    TopicAdminClient topicAdmin = TopicAdminClient.create(
        TopicAdminSettings.newBuilder()
            .setTransportChannelProvider(channelProvider)
            .setCredentialsProvider(credentialsProvider)
            .build());
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    // EmulatorDetection reads PUBSUB_EMULATOR_HOST and switches to the insecure path.
    var publisher = await new PublisherServiceApiClientBuilder
    {
        EmulatorDetection = EmulatorDetection.EmulatorOnly
    }.BuildAsync();
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby
    # Ruby does not auto-detect the emulator — pass emulator_host: explicitly.
    require "google/cloud/pubsub"

    pubsub = Google::Cloud::PubSub.new(
      project_id: ENV["PUBSUB_PROJECT_ID"],
      emulator_host: ENV["PUBSUB_EMULATOR_HOST"], # e.g. localhost:8085
    )
    ```
  </Tab>
</Tabs>

## No auth, no TLS [#no-auth-no-tls]

The connector runs in **emulator mode**: no Google OAuth2/JWT validation, no IAM enforcement (the
`IAMPolicy` RPCs are permissive stubs), and **no TLS**. The transport is **insecure gRPC**.

<Callout type="warn">
  **Do not expose port 8085 to untrusted networks.** This no-auth, no-TLS posture is by design and
  matches Google's local emulator. Run it on a trusted network or behind your own perimeter. For the
  shared security model across connectors, see
  [Auth & security](/connectors/reference/auth-and-security).
</Callout>

DoS guards remain active even with no auth:

* `CONNECTORS_GCP_MAX_MESSAGE_BYTES` — a gRPC receive-size body cap (default 10 MiB);
* `CONNECTORS_GCP_MAX_INFLIGHT_PER_SUBSCRIPTION` — hard cap on leased (un-acked) messages per
  subscription (20,000);
* `CONNECTORS_GCP_MAX_CONCURRENT_POLLS` — poller slot budget (1,024);
* `CONNECTORS_GCP_MAX_SEEK_REPLAY` — max messages a single `Seek` may replay (1,000,000);
* push-delivery backoff.

These knobs are documented in [Configuration](/connectors/gcp-pub-sub/concepts/configuration).

## The advertised-endpoint hint [#the-advertised-endpoint-hint]

`CONNECTORS_GCP_ADVERTISED_ENDPOINT` (default empty) is a **cosmetic** `host:port` string the
dashboard shows as the suggested `PUBSUB_EMULATOR_HOST` value. It does not change how the listener
binds or behaves; set it to the externally reachable address (e.g. `kubemq.mycompany.svc:8085`) so
operators copy the right host into their env.

## gRPC only — no REST/JSON [#grpc-only--no-restjson]

<Callout type="warn">
  **The connector is gRPC-only.** There is **no REST/JSON v1** (grpc-gateway) surface. Clients and
  tools that speak only the Pub/Sub REST API will not work; use a gRPC client library or `gcloud`
  (which uses gRPC for the emulator). See
  [Capabilities](/connectors/gcp-pub-sub/reference/capabilities).
</Callout>

## Periodic stream reconnect [#periodic-stream-reconnect]

A `StreamingPull` stream is closed by the server after `CONNECTORS_GCP_STREAM_CLOSE_SECONDS`
(default **1800 s** / 30 min) with `UNAVAILABLE`; client libraries **transparently reconnect**.
This bounds per-stream resource lifetime and is normal — your receive callback keeps running across
the reconnect. See [Subscribing](/connectors/gcp-pub-sub/how-to/subscribing).

## Sticky-stream caveat (cluster) [#sticky-stream-caveat-cluster]

<Callout type="warn">
  **Node-local state needs a sticky load balancer.** Topic / subscription / snapshot / schema
  **records** are synchronized across cluster nodes (a per-node replicated registry, last-writer-wins),
  but two pieces of delivery state are **node-local**:

  * **Exactly-once `ack_id`s** — an `ack_id` minted on one node is invalid on another (its node id
    won't match), so an exactly-once subscription's `StreamingPull` traffic must be pinned to one
    node;
  * **`StreamingPull` leases / in-flight tracking** — leased messages and flow-control counters live
    on the node that delivered them.

  Cluster deployments must put a **sticky load balancer** (session affinity) in front of the
  connector so each subscriber sticks to one node for the lifetime of its in-flight messages.
  Single-node deployments are unaffected. See
  [Reliability](/connectors/gcp-pub-sub/how-to/reliability).
</Callout>

Message **data** itself is not replicated by the connector — it rides the existing Events Store /
Queues replication.

## Traffic gate [#traffic-gate]

While the message broker is not ready, the traffic-gate interceptor short-circuits requests; on a
not-ready → ready transition the connector **drops all in-memory leases** (their downstream
transactions are dead) and the poller rebuilds. SDKs see a transient `UNAVAILABLE` and retry.

## Related [#related]

<Cards>
  <Card title="Configuration" href="/connectors/gcp-pub-sub/concepts/configuration" description="The opt-in CONNECTORS_GCP_ENABLE flag, gRPC port 8085, and the thirteen CONNECTORS_GCP_* settings and their defaults." />

  <Card title="Subscribing" href="/connectors/gcp-pub-sub/how-to/subscribing" description="Pull vs StreamingPull, the ack-deadline lease, flow control, and the periodic reconnect in depth." />

  <Card title="Auth & security" href="/connectors/reference/auth-and-security" description="The shared security model across KubeMQ connectors, including TLS via the server Security block." />
</Cards>
