Stackdriver Monitoring `The set of resource labels is incomplete. Missing labels: (container_name namespace_name).` エラーの調査

# TL;DR

- Monitored Resourceのラベルは必須パラメータ
- GKE, Opencensus-python, stackdriver monitoring環境で `Missing labels: (container_name namespace_name)`が出たら環境変数に`NAMESPACE`と`CONTAINER_NAME`を設定する

# 概要

[前回の記事](https://qiita.com/amuyikam/items/d5bb2320126b6a530577)で、OpenCensusを利用してStackdriver Monitoringに測定値データを送信する簡易なアプリケーションを作成しました。
後半となる本記事では、前半のアプリケーションをGKE上で動かします。そしてその際に出る次のエラーについて原因と解決方法を記載します。

```
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
        status = StatusCode.INVALID_ARGUMENT
        details = "One or more TimeSeries could not be written: The set of resource labels is incomplete. Missing labels: (container_name namespace_name).: timeSeries[0-5]"
        debug_error_string = "{"created":"@1568274913.190983719","description":"Error received from peer ipv4:172.217.25.106:443","file":"src/core/lib/surface/call.cc","file_line":1052,"grpc_message":"One or more TimeSeries could not be written: The set of resource labels is incomplete. Missing labels: (container_name namespace_name).: timeSeries[0-5]","grpc_status":3}"
```

# GKE

GKE(Google Kubernetes Engine)はGoogleのManaged Kubernetes Serviceです。
GKEノードそのもののmetricもですが、containerのGCP metricも自動でstackdriverに収集されます。
今回はcontainer内のcustom metricをstackdriver monitoringに送信することを目指します。


# アプリケーションの修正

前回作成したアプリケーションをGKE上で動かすよう修正します。

## 認証

アプリケーションにstackdriver monitoringの書き込み権限(`roles/monitoring.metricWriter`)を与えるために前回はユーザのOAuth2.0認証を利用していましたが、GKE上ではユーザ認証はできません(というよりはするべきではありません)。
ServiceAccountを作成しても良いのですが、今回はもっと簡単な方法が利用できます。

GCP上で動かすアプリケーションには、GCPのサービス同士の連携を楽にするためデフォルトでサービスアカウントが利用できます。ドキュメント上では4通りの認証手段のうち`Environment-provided service account`として説明されています。
https://cloud.google.com/docs/authentication/

> If your application runs on Compute Engine, Kubernetes Engine, the App Engine flexible environment, or Cloud Functions, you don't need to create your own service account. Compute Engine includes a default service account that is automatically created for you
https://cloud.google.com/docs/authentication/production#obtaining_credentials_on_compute_engine_kubernetes_engine_app_engine_flexible_environment_and_cloud_functions

このため、GCPインスタンス上であればアプリケーションで特に認証行為をしないよう既存のコードを変更します。以下のコード片ではちょうどopencensusのコードに同じ処理があるのでそれを利用しています。

```python:実行環境がGCPかどうかを判定する
from opencensus.common.monitored_resource.gcp_metadata_config import GcpMetadataConfig

gcp_metadata_config = GcpMetadataConfig()
if gcp_metadata_config.is_running:
    project_id = gcp_metadata_config.get_gce_metadata()['project/project-id']

```

## Dockerfile

GKEの上で動かすため、前回のコードを次の様にDockerfileでdocker imageに固めます。

```dockerfile:Dockerfile

from python:3.7
USER root

ADD main.py /app/main.py
ADD requirements.txt /app/requirements.txt

WORKDIR /app

RUN pip install -r requirements.txt

ENTRYPOINT ["python", "-u", "main.py"]
```

これをbuildしてimage registryにuploadしておきます。
(この辺は実際にはgithub -> Google Cloud Build -> Google Container Registryの連携で楽をします。)

## Deployment

GKEにDeployするためのPod定義を次のようにしました。

```yaml:deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: opencensus-stackdriver-sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: opencensus-stackdriver-sample
  template:
    metadata:
      labels:
        app: opencensus-stackdriver-sample
    spec:
      containers:
        - name: opencensus-stackdriver-sample
          image: gcr.io/<YOUR IMAGE REPOSITORY>/opencensus-stackdriver-sample:<HASH>
```

# 問題の再現

コードの修正が終わったので、GKE上で動かしてみます。
うまく行けばStackdriver Monitoringにデータが入ってくるはずです。

```console
$ kubectl apply -f deployment.yaml
deployment.apps/opencensus-stackdriver-sample created
```

Deploymentの設定は成功しましたが、期待して待っていてもデータは送信されず、ログを見ると次のようなエラーが出ています。

```console
$ kubectl logs opencensus-stackdriver-sample-fc6449cf4-8mpm5 opencensus-stackdriver-sample
Error handling metric export
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/google/api_core/grpc_helpers.py", line 57, in error_remapped_callable
    return callable_(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/grpc/_channel.py", line 565, in __call__
    return _end_unary_response_blocking(state, call, False, None)
  File "/usr/local/lib/python3.7/site-packages/grpc/_channel.py", line 467, in _end_unary_response_blocking
    raise _Rendezvous(state, None, None, deadline)
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
        status = StatusCode.INVALID_ARGUMENT
        details = "One or more TimeSeries could not be written: The set of resource labels is incomplete. Missing labels: (container_name namespace_name).: timeSeries[0]"
        debug_error_string = "{"created":"@1568876145.710983148","description":"Error received from peer ipv4:216.58.197.138:443","file":"src/core/lib/surface/call.cc","file_line":1052,"grpc_message":"One or more TimeSeries could not be written: The set of resource labels is incomplete. Missing labels: (container_name namespace_name).: timeSeries[0]","grpc_status":3}"
>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/opencensus/metrics/transport.py", line 59, in func
    return self.func(*aa, **kw)
  File "/usr/local/lib/python3.7/site-packages/opencensus/metrics/transport.py", line 113, in export_all
    export(itertools.chain(*all_gets))
  File "/usr/local/lib/python3.7/site-packages/opencensus/ext/stackdriver/stats_exporter/__init__.py", line 162, in export_metrics
    self.client.project_path(self.options.project_id), ts_batch)
  File "/usr/local/lib/python3.7/site-packages/google/cloud/monitoring_v3/gapic/metric_service_client.py", line 1024, in create_time_series
    request, retry=retry, timeout=timeout, metadata=metadata
  File "/usr/local/lib/python3.7/site-packages/google/api_core/gapic_v1/method.py", line 143, in __call__
    return wrapped_func(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/google/api_core/retry.py", line 273, in retry_wrapped_func
    on_error=on_error,
  File "/usr/local/lib/python3.7/site-packages/google/api_core/retry.py", line 182, in retry_target
    return target()
  File "/usr/local/lib/python3.7/site-packages/google/api_core/timeout.py", line 214, in func_with_timeout
    return func(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/google/api_core/grpc_helpers.py", line 59, in error_remapped_callable
    six.raise_from(exceptions.from_grpc_error(exc), exc)
  File "<string>", line 3, in raise_from
google.api_core.exceptions.InvalidArgument: 400 One or more TimeSeries could not be written: The set of resource labels is incomplete. Missing labels: (container_name namespace_name).: timeSeries[0]
Error handling metric export
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/google/api_core/grpc_helpers.py", line 57, in error_remapped_callable
    return callable_(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/grpc/_channel.py", line 565, in __call__
    return _end_unary_response_blocking(state, call, False, None)
  File "/usr/local/lib/python3.7/site-packages/grpc/_channel.py", line 467, in _end_unary_response_blocking
    raise _Rendezvous(state, None, None, deadline)
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
        status = StatusCode.INVALID_ARGUMENT
        details = "One or more TimeSeries could not be written: The set of resource labels is incomplete. Missing labels: (container_name namespace_name).: timeSeries[0]"
        debug_error_string = "{"created":"@1568876205.539466768","description":"Error received from peer ipv4:216.58.197.138:443","file":"src/core/lib/surface/call.cc","file_line":1052,"grpc_message":"One or more TimeSeries could not be written: The set of resource labels is incomplete. Missing labels: (container_name namespace_name).: timeSeries[0]","grpc_status":3}"
>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/opencensus/metrics/transport.py", line 59, in func
    return self.func(*aa, **kw)
  File "/usr/local/lib/python3.7/site-packages/opencensus/metrics/transport.py", line 113, in export_all
    export(itertools.chain(*all_gets))
  File "/usr/local/lib/python3.7/site-packages/opencensus/ext/stackdriver/stats_exporter/__init__.py", line 162, in export_metrics
    self.client.project_path(self.options.project_id), ts_batch)
  File "/usr/local/lib/python3.7/site-packages/google/cloud/monitoring_v3/gapic/metric_service_client.py", line 1024, in create_time_series
    request, retry=retry, timeout=timeout, metadata=metadata
  File "/usr/local/lib/python3.7/site-packages/google/api_core/gapic_v1/method.py", line 143, in __call__
    return wrapped_func(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/google/api_core/retry.py", line 273, in retry_wrapped_func
    on_error=on_error,
  File "/usr/local/lib/python3.7/site-packages/google/api_core/retry.py", line 182, in retry_target
    return target()
  File "/usr/local/lib/python3.7/site-packages/google/api_core/timeout.py", line 214, in func_with_timeout
    return func(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/google/api_core/grpc_helpers.py", line 59, in error_remapped_callable
    six.raise_from(exceptions.from_grpc_error(exc), exc)
  File "<string>", line 3, in raise_from
google.api_core.exceptions.InvalidArgument: 400 One or more TimeSeries could not be written: The set of resource labels is incomplete. Missing labels: (container_name namespace_name).: timeSeries[0]
```

長いですが、重要な部分は次の箇所です。
要するにStackdriver MonitoringのgRPC APIを叩いたが、`INVALID_ARGUMENT`が返ってきた、エラーメッセージは `The set of resource labels is incomplete. Missing labels: (container_name namespace_name)` です。

```
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
        status = StatusCode.INVALID_ARGUMENT
        details = "One or more TimeSeries could not be written: The set of resource labels is incomplete. Missing labels: (container_name namespace_name).: timeSeries[0]"
        debug_error_string = "{"created":"@1568876145.710983148","description":"Error received from peer ipv4:216.58.197.138:443","file":"src/core/lib/surface/call.cc","file_line":1052,"grpc_message":"One or more TimeSeries could not be written: The set of resource labels is incomplete. Missing labels: (container_name namespace_name).: timeSeries[0]","grpc_status":3}"
```

# 調査

エラーコードやエラーメッセージを検索してもあまり有効な情報が得られなかったので、エラーメッセージを頼りに原因を調べていきます。
エラーメッセージは`resource labels`が不完全で`container_name`と`namespace_name`のラベルが足りないと言っています。OpenCensusにはTagという用語はありますがlabelという用語はないため、stackdriverに注目します。

## Structure of Metric Types

[Structure of Metric Types](https://cloud.google.com/monitoring/api/v3/metrics-details)に、Stackdriver Monitoringの概念の概要が記載されています**が、これを見ても混乱するので読み飛ばします**(私にはわからなかった)

一応上記ページでmonitored resourceの例の中に何の説明もなくlabelsが登場するのですが、同ページ中でlabelといえばmetric labelの説明しかないため予め概念が頭に入っていないと混同する(私はした)

## Stackdriver Monitoring gRPC API

エラー箇所のAPIを調べて必要なパラメータを確認します。

```
  File "/usr/local/lib/python3.7/site-packages/google/cloud/monitoring_v3/gapic/metric_service_client.py", line 1024, in create_time_series
    request, retry=retry, timeout=timeout, metadata=metadata
```

stacktraceから[このあたり](https://github.com/googleapis/google-cloud-python/blob/f890d2916e67e40dfbdfeb814d6b92fce9103f5b/monitoring/google/cloud/monitoring_v3/gapic/metric_service_client.py#L940)で[CreateTimeSeriesRequest](https://cloud.google.com/monitoring/api/ref_v3/rpc/google.monitoring.v3#createtimeseriesrequest)を呼んでエラーが出ていることがわかります。
`CreateTimeSeriesRequest`は測定したTimeSeriesをPOSTする処理です。パラメータに[TimeSeries](https://cloud.google.com/monitoring/api/ref_v3/rpc/google.monitoring.v3#google.monitoring.v3.TimeSeries)のリストを取ります。

TimeSeriesのパラメータは次のようになっていて、これを見ると何が起きたかがだんだんわかってきます。

- [TimeSeries](https://cloud.google.com/monitoring/api/ref_v3/rpc/google.monitoring.v3#google.monitoring.v3.TimeSeries)
  - [Metric](https://cloud.google.com/monitoring/api/ref_v3/rpc/google.api#google.api.Metric)
  - [MonitoredResource](https://cloud.google.com/monitoring/api/ref_v3/rpc/google.api#google.api.MonitoredResource)
  - [MetricKind](https://cloud.google.com/monitoring/api/ref_v3/rpc/google.api#google.api.MetricDescriptor.MetricKind)
  - [ValueType](https://cloud.google.com/monitoring/api/ref_v3/rpc/google.api#google.api.MetricDescriptor.ValueType)
  - [Point](https://cloud.google.com/monitoring/api/ref_v3/rpc/google.monitoring.v3#google.monitoring.v3.Point)

![図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266939/37bc4f1b-37b1-df5d-559a-23108bfe782c.png)

TimeSeriesの中でlabelsは2箇所で登場しますが、どうやら**MetricのlabelsとMonitoredResourceのlabelsは別物**のようです。

## Monitored ResourceとMetric

Monitoring ResourceとMetricについて確認します。
唐突ですが、家の庭に植木鉢Aと植木鉢Bがあり、その温度と湿度を監視したいとします。
これは現実的にはあるデータセンターにあるサーバAとサーバBのCPU使用率や同時接続数のことです。

### Monitored Resource

Monitored Resourceはtypeとlabelsから成り、ある一意の監視対象リソースを表します。
植木鉢Aと植木鉢Bがそれぞれ個別のMonitored Resourceです。

Monitored Resourceのtypeは監視対象の種類を表しており、代表的なtypeについては予め定義されています。例えば`gce-instance`はCompute Engineのインスタンス、`aws_lambda_function`はAWSのlambda functionです。
カスタムなMonitored Resourceを追加することはできません。規定のtypeにうまく当てはまらない監視対象があったら、`generic_node`または`generic_task`を選択します。それらにも当てはまらない場合の最終手段として`global`が用意されています。
植木鉢タイプが予め設定されてあれば良いのですがそんなものはないので、`generic_node`タイプを利用します。植木鉢Aと植木鉢Bは共に`generic_node`タイプとしましょう。

labelは同じtypeのリソース同士を**一意に**区別するためのパラメータです。
既定typeごとにlabelが予め定められています。例えば`generic_node`タイプは`project_id`, `location`, `namespace`, `node_id`の4つのラベルでリソースを区別します。
植木鉢Aと植木鉢Bは`project_id`, `location`, `namespace`までは同じで、node_idをAとBとして区別すると良さそうです。

リソース同士を一意に区別する必要があるため、labelは必須パラメータになっています。

### Metric

Metricは`cpu usage`や`http response latency`などの監視項目(または指標)を表します。

Metricのtypeは監視項目の種類を表します。
植木鉢監視の場合、温度と湿度です。
代表的な項目は[GCP Metrics](https://cloud.google.com/monitoring/api/metrics_gcp)として予め用意されていますが、任意のカスタム指標を追加することができます。

Metricのlabelは監視項目をフィルタリングするために使うようです。
例えば、湿度Metricに天気というラベルを用意しておくと、晴れの日の湿度と雨の日の湿度のグラフを比較することができます。
Metricのラベルは補助的な情報であるため任意パラメータです。

## Stackdriver調査まとめ

今回はエラーメッセージから[MonitoredResource](https://cloud.google.com/monitoring/api/ref_v3/rpc/google.api#google.api.MonitoredResource)の必須ラベルが足りないものと推測します。

Monitored Resource Typeは、GKE上のコンテナなのでおそらく [`gke_container`](https://cloud.google.com/monitoring/api/resources#tag_gke_container
) か [`k8s_container`](https://cloud.google.com/monitoring/api/resources#tag_k8s_container)です。エラーメッセージでは`namespace_id`ではなく`namespace_name`が足りないと言っているため `k8s_container`の方でしょう。
このタイプのResourceは次のlabelを設定する必要があります。

> k8s_container
> - project_id: The identifier of the GCP project associated with this resource, such as "my-project".
> - location: The physical location of the cluster that contains the container.
> - cluster_name: The name of the cluster that the container is running in.
> - namespace_name: The name of the namespace that the container is running in.
> - pod_name: The name of the pod that the container is running in.
> - container_name: The name of the container.
>  
> https://cloud.google.com/monitoring/api/resources#tag_k8s_container

この内、`container_name`と`namespace_name`が足りないようです。
逆に言うと、`project_id`, `location`, `cluster_name`, `pod_name` はAPI callに含まれているようです。誰の仕業でしょう。

## OpenCensus-Python

そもそも、サンプルコードではMonitored Resource Typeもラベルも登場しませんでした。
`gke_container`と指定した覚えも`cluster_name`や`pod_id`を設定した覚えもありません。
APIを呼ぶ際にopencensusかgoogle cloudクライアントライブラリあたりでラベルを設定しているのだと思います。気になる箇所の処理を読めるのがOSSの良い点なので追っていきましょう。

### Monitored Resource Type判定

Monitored Resourceについてはopencensusのstackdriver拡張の[`set_monitored_resource`](https://github.com/census-instrumentation/opencensus-python/blob/af284a92b80bcbaf5db53e7e0813f96691b4c696/contrib/opencensus-ext-stackdriver/opencensus/ext/stackdriver/stats_exporter/__init__.py#L305)を見れば何をしているかがわかります。

[`monitored_resource.get_instance`](https://github.com/census-instrumentation/opencensus-python/blob/3f01007a05dcae84cc912a28588d3074fff4f587/opencensus/common/monitored_resource/monitored_resource.py#L37)でMonitored Resource typeの判定が行われており、`gce_instance`, `aws_ec2_instance`, `k8s_container`の判定をして、typeが判明したらそれに対応するlabelを取得します。

> ```python:k8s_container_typeのmonitored_resourceを設定するところ
> if k8s_utils.is_k8s_environment():
>     resources.append(resource.Resource(
>         _K8S_CONTAINER, k8s_utils.get_k8s_metadata()))
> ```
> 
> https://github.com/census-instrumentation/opencensus-python/blob/3f01007a05dcae84cc912a28588d3074fff4f587/opencensus/common/monitored_resource/monitored_resource.py#L51

### Monitored Reosurce label取得

更に追っていくと`k8s_container`のラベルは2つの観点で集められています。

1. GCE metadata serverから次のパラメータを取得する

- PROJECT_ID_KEY: 'project/project-id'
- INSTANCE_ID_KEY: 'instance/id'
- ZONE_KEY: 'instance/zone'
- CLUSTER_NAME_KEY = 'instance/attributes/cluster-name'

https://github.com/census-instrumentation/opencensus-python/blob/3f01007a05dcae84cc912a28588d3074fff4f587/opencensus/common/monitored_resource/gcp_metadata_config.py#L95

GCPにはメタデータサーバという仕組みが用意されており、インスタンスの中からメタデータを取得できるようです。
`project/project-id`, `instance/id`, `instance/zone`, `instance/attributes/cluster-name`については[default metadata keys](https://cloud.google.com/compute/docs/storing-retrieving-metadata#default) で確認できます。

2.もし次の環境変数が設定されていればその値を取得する

- CONTAINER_NAME_KEY: 'CONTAINER_NAME'
- NAMESPACE_NAME_KEY: 'NAMESPACE'
- POD_NAME_KEY: 'HOSTNAME'

特に上記の環境変数を設定した覚えはないです。
実際に動いているPodを見てみましょう。

```console
$ kubectl exec opencensus-stackdriver-sample-fc6449cf4-nrspv env
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=opencensus-stackdriver-sample-fc6449cf4-nrspv
(省略)
```

`HOSTNAME`は設定されていますが、`NAMESPACE`と`CONTAINER_NAME`は設定されていませんでした。
[kubernetesのドキュメント](https://kubernetes.io/docs/concepts/containers/container-environment-variables/#container-information)には、このあたりの環境変数については次のように書かれています。

> The hostname of a Container is the name of the Pod in which the Container is running. It is available through the hostname command or the gethostname function call in libc.
>
> The Pod name and namespace are available as environment variables through the downward API.

環境変数`HOSTNAME`を設定しているのはkubernetesではなくbashがgethostname関数を利用して自動で設定しているものと思うので、ベースのイメージによっては`HOSTNAME`も無い場合がありそうです。これは別途追ってみるのも面白いかもしれません。

### API parameterの設定

取得したMonitored Resourceのtypeとlabelを設定します。
ここでAPI callに利用するkeyと先ほど取得したlabelのkeyを対応させます。

> ```python
>      if resource_type == 'k8s_container':
>        series.resource.type = 'k8s_container'
>        set_attribute_label(gcp_metadata_config.PROJECT_ID_KEY, 'project_id')
>        set_attribute_label(k8s_utils.CLUSTER_NAME_KEY, 'cluster_name')
>        set_attribute_label(k8s_utils.CONTAINER_NAME_KEY, 'container_name')
>        set_attribute_label(k8s_utils.NAMESPACE_NAME_KEY, 'namespace_name')
>        set_attribute_label(k8s_utils.POD_NAME_KEY, 'pod_name')
>        set_attribute_label(gcp_metadata_config.ZONE_KEY, 'location')
> ```
> 
> https://github.com/census-instrumentation/opencensus-python/blob/af284a92b80bcbaf5db53e7e0813f96691b4c696/contrib/opencensus-ext-stackdriver/opencensus/ext/stackdriver/stats_exporter/__init__.py#L339

## OpenCensus-Python調査まとめ

随分とわかってきました。

APIパラメータのうち、`project_id`, `location`, `cluster_name`はGCP metadata serverから取得します。GCPがこれらのパラメータはデフォルトで提供してくれています。
`container_name`, `namespace_name`, `pod_name` は環境変数から取得します。`pod_name`に対応する`HOSTNAME`はkubernetesが勝手に挿入してくれるようですが、`CONTAINER_NAME`と`NAMESPACE`は勝手には挿入してくれないようです。

この調査結果はエラーメッセージ `The set of resource labels is incomplete. Missing labels: (container_name namespace_name)` と一致します。

# 対策

## Kubernetes downward API

どうも環境変数を足してあげればうまく動きそうです。

`CONTAINER_NAME`と`NAMESPACE`
PodやContainerに関する[このあたりのパラメータ](https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/#capabilities-of-the-downward-api)をcontainerに環境変数として渡せます。

`deployment.yaml`に次の行を足して、`NAMESPACE`と`CONTAINER_NAME`を入れてやります。
コンテナ名を動的に環境変数に取得する方法がわからなかった(できない?)ためCONTAINER_NAMEは静的に設定しています。

```yaml:deployment.yaml
          env:
            - name: NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: CONTAINER_NAME
              value: "opencensus-stackdriver-sample"
```

## 確認

動かしてみましょう。

```
$ kubectl apply -f deployment.yaml
deployment.apps/opencensus-stackdriver-sample configured
```

<img width="635" alt="スクリーンショット 2019-10-08 1.31.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266939/707a6129-97f2-763c-973e-fd89c6b1e978.png">

Podを起動して暫く待つと、Stackdriver Monitoring Consoleでpodからのmetricが流れてきているのが確認できます。成功しました🎉
monitored resource labelもちゃんと設定できているようです。

# まとめ

GKE上のコンテナからOpenCensus-pythonでstackdriverにmetricを送る場合、gRPC APIでエラーが返されます。このエラーを解消するためには環境変数`NAMESPACE`と`CONTAINER_NAME`を明に設定する必要があります。

流石にそれはライブラリ側で解決してくれーと思ってissueをあげようとしたらこの記事を書いている間にこの事象のissueが上がっていました。🥳
https://github.com/census-instrumentation/opencensus-python/issues/796