TL;DR
- Monitored Resourceのラベルは必須パラメータ
- GKE, Opencensus-python, stackdriver monitoring環境で
Missing labels: (container_name namespace_name)
が出たら環境変数にNAMESPACE
とCONTAINER_NAME
を設定する
概要
前回の記事で、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のコードに同じ処理があるのでそれを利用しています。
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に固めます。
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定義を次のようにしました。
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にデータが入ってくるはずです。
$ kubectl apply -f deployment.yaml
deployment.apps/opencensus-stackdriver-sample created
Deploymentの設定は成功しましたが、期待して待っていてもデータは送信されず、ログを見ると次のようなエラーが出ています。
$ 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に、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からこのあたりでCreateTimeSeriesRequestを呼んでエラーが出ていることがわかります。
CreateTimeSeriesRequest
は測定したTimeSeriesをPOSTする処理です。パラメータにTimeSeriesのリストを取ります。
TimeSeriesのパラメータは次のようになっていて、これを見ると何が起きたかがだんだんわかってきます。
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として予め用意されていますが、任意のカスタム指標を追加することができます。
Metricのlabelは監視項目をフィルタリングするために使うようです。
例えば、湿度Metricに天気というラベルを用意しておくと、晴れの日の湿度と雨の日の湿度のグラフを比較することができます。
Metricのラベルは補助的な情報であるため任意パラメータです。
Stackdriver調査まとめ
今回はエラーメッセージからMonitoredResourceの必須ラベルが足りないものと推測します。
Monitored Resource Typeは、GKE上のコンテナなのでおそらく gke_container
か 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
を見れば何をしているかがわかります。
monitored_resource.get_instance
でMonitored Resource typeの判定が行われており、gce_instance
, aws_ec2_instance
, k8s_container
の判定をして、typeが判明したらそれに対応するlabelを取得します。
k8s_container_typeのmonitored_resourceを設定するところif k8s_utils.is_k8s_environment(): resources.append(resource.Resource( _K8S_CONTAINER, k8s_utils.get_k8s_metadata()))
Monitored Reosurce label取得
更に追っていくとk8s_container
のラベルは2つの観点で集められています。
- 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'
GCPにはメタデータサーバという仕組みが用意されており、インスタンスの中からメタデータを取得できるようです。
project/project-id
, instance/id
, instance/zone
, instance/attributes/cluster-name
についてはdefault metadata keys で確認できます。
2.もし次の環境変数が設定されていればその値を取得する
- CONTAINER_NAME_KEY: 'CONTAINER_NAME'
- NAMESPACE_NAME_KEY: 'NAMESPACE'
- POD_NAME_KEY: 'HOSTNAME'
特に上記の環境変数を設定した覚えはないです。
実際に動いているPodを見てみましょう。
$ 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のドキュメントには、このあたりの環境変数については次のように書かれています。
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を対応させます。
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')
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に関するこのあたりのパラメータをcontainerに環境変数として渡せます。
deployment.yaml
に次の行を足して、NAMESPACE
とCONTAINER_NAME
を入れてやります。
コンテナ名を動的に環境変数に取得する方法がわからなかった(できない?)ためCONTAINER_NAMEは静的に設定しています。
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

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