Skip to content

Instantly share code, notes, and snippets.

@nota-ja
Last active December 11, 2017 15:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nota-ja/461c8caca32c8e03e849f7790e11f8dc to your computer and use it in GitHub Desktop.
Save nota-ja/461c8caca32c8e03e849f7790e11f8dc to your computer and use it in GitHub Desktop.
アプリの実行環境の環境変数を知る方法

アプリの実行環境の環境変数を知る方法

この Gist は Cloud Foundry Advent Calendar 2017 の12日目の記事です。


TL;DR

アプリの実行環境の環境変数を知るには, cf ssh でアプリのコンテナーに入って,以下のスクリプトを実行すれば(たぶん)OK:

cat /proc/$(ps -ef | grep -v diego-sshd | awk ' $1 == "vcap" { print $0 } ' | awk ' $3 == 0 { print $2 } ' | sort -n | head -n 1)/environ | sed -e 's/\x0/\n/g'

詳細

この Advent Calendar の 12/6の私の記事 で,「task を実行する際は PATH 上に何があるのかを知ることが重要」ということを書いたが,今回は, PATH も含め,アプリの環境変数をどうやって知ればいいのかという話。

使用するサンプル

以下,このアプリ を以下の manifest.yml を使ってデプロイしたものを例として用いる。

manifest.yml:

---
applications:
  - name: nodetask
    env:
      MY_ENV: my-value

cf env

アプリの環境変数は, cf env <アプリ名> で取得できるのでは,と思われるかもしれないが,これでわかるのはごく一部で, PATHHOME も知ることはできない。

例:

$ cf env nodetask
Getting env variables for app nodetask in org example-org / space example-spc as nota...
OK

System-Provided:


{
 "VCAP_APPLICATION": {
  "application_id": "a5b6ab92-90b8-401f-b3b8-c51ebee8ef52",
  "application_name": "nodetask",
  "application_uris": [
   "nodetask.example.org"
  ],
  "application_version": "e4551ace-13c3-4a15-942b-3d268efad711",
  "cf_api": "https://api.example.org",
  "limits": {
   "disk": 128,
   "fds": 16384,
   "mem": 128
  },
  "name": "nodetask",
  "space_id": "930a3384-7b8c-4262-a083-8cc11db48f0f",
  "space_name": "example-spc",
  "uris": [
   "nodetask.example.org"
  ],
  "users": null,
  "version": "e4551ace-13c3-4a15-942b-3d268efad711"
 }
}

User-Provided:
MY_ENV: my-value

No running env variables have been set

No staging env variables have been set

cf ssh

それなら cf ssh <アプリ名> で実際にコンテナーに入って調べれば…ということになりそうだが,これも実はそう単純ではない。

例:

$ cf ssh nodetask
$ env
CF_INSTANCE_ADDR=10.0.16.22:62543
TERM=xterm
VCAP_APP_PORT=8080
USER=vcap
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:
CF_INSTANCE_INTERNAL_IP=10.254.0.2
VCAP_APPLICATION={"cf_api":"https://api.example.org","limits":{"fds":16384,"mem":128,"disk":128},"application_name":"nodetask","application_uris":["nodetask.example.org"],"name":"nodetask","space_name":"example-spc","space_id":"930a3384-7b8c-4262-a083-8cc11db48f0f","uris":["nodetask.example.org"],"application_id":"a5b6ab92-90b8-401f-b3b8-c51ebee8ef52","version":"e4551ace-13c3-4a15-942b-3d268efad711","application_version":"e4551ace-13c3-4a15-942b-3d268efad711"}
CF_INSTANCE_GUID=6ddbf04f-b643-4d87-7844-9fc6
PATH=/bin:/usr/bin
PWD=/home/vcap
LANG=en_US.UTF-8
MY_ENV=my-value
CF_INSTANCE_PORT=62543
CF_INSTANCE_IP=10.0.16.22
VCAP_SERVICES={}
CF_INSTANCE_INDEX=0
SHLVL=1
HOME=/home/vcap
CF_INSTANCE_PORTS=[{"external":62543,"internal":8080},{"external":62544,"internal":2222}]
LESSOPEN=| /bin/lesspipe %s
INSTANCE_INDEX=0
PORT=8080
INSTANCE_GUID=6ddbf04f-b643-4d87-7844-9fc6
VCAP_APP_HOST=0.0.0.0
LESSCLOSE=/bin/lesspipe %s %s
MEMORY_LIMIT=128m
_=/usr/bin/env

一見大丈夫そうだが,Node.js に関係する情報が何も含まれていない。

なぜか。

理由は単純で,アプリの実行プロセスは cf ssh で生成されたシェルのプロセスとは何の関係もないからである。

$ ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 14:40 ?        00:00:00 /proc/self/exe init
vcap          13       0  0 14:40 ?        00:00:00 /tmp/lifecycle/diego-sshd --allowedKeyExchanges= --address=0.0.0.0:2222 --allowUnauthenticatedClients=false
vcap          14       0  0 14:40 ?        00:00:00 npm
vcap          66      14  0 14:40 ?        00:00:00 sh -c node server.js
vcap          67      66  0 14:40 ?        00:00:00 node server.js
vcap         124      13  0 14:42 pts/0    00:00:00 /bin/bash
vcap         421     124  0 14:56 pts/0    00:00:00 ps -ef

アプリの起動に関係するプロセス木は 14 => 66 => 67 であり,cf ssh のシェルのプロセス木は 13 => 124 である。前者と後者は互いに独立している。

cf ssh+cat /proc/$(アプリ起動プロセスPID)/environ

アプリの実行環境の環境変数を知るためには,プロセス 14 の環境変数を調べればよい。

これは /proc/14/environ に入ってるので, cat で覗いてみる:

$ cat /proc/14/environ
CF_INSTANCE_ADDR=10.0.16.22:62543TMPDIR=/home/vcap/tmpWEB_MEMORY=512MEMORY_AVAILABLE=128VCAP_APP_PORT=8080USER=vcapCF_INSTANCE_INTERNAL_IP=10.254.0.2VCAP_APPLICATION={"application_id":"a5b6ab92-90b8-401f-b3b8-c51ebee8ef52","application_name":"nodetask","application_uris":["nodetask.example.org"],"application_version":"e4551ace-13c3-4a15-942b-3d268efad711","cf_api":"https://api.example.org","host":"0.0.0.0","instance_id":"6ddbf04f-b643-4d87-7844-9fc6","instance_index":0,"limits":{"disk":128,"fds":16384,"mem":128},"name":"nodetask","port":8080,"space_id":"930a3384-7b8c-4262-a083-8cc11db48f0f","space_name":"example-spc","uris":["nodetask.example.org"],"version":"e4551ace-13c3-4a15-942b-3d268efad711"}CF_INSTANCE_GUID=6ddbf04f-b643-4d87-7844-9fc6PATH=/home/vcap/deps/0/bin:/usr/local/bin:/usr/bin:/bin:/home/vcap/app/bin:/home/vcap/app/node_modules/.binWEB_CONCURRENCY=1PWD=/home/vcap/appLANG=en_US.UTF-8MY_ENV=my-valueCF_INSTANCE_PORT=62543NODE_ENV=productionVCAP_SERVICES={}CF_INSTANCE_IP=10.0.16.22CF_INSTANCE_INDEX=0HOME=/home/vcap/appSHLVL=1DEPS_DIR=/home/vcap/depsCF_INSTANCE_PORTS=[{"external":62543,"internal":8080},{"external":62544,"internal":2222}]INSTANCE_INDEX=0PORT=8080INSTANCE_GUID=6ddbf04f-b643-4d87-7844-9fc6VCAP_APP_HOST=0.0.0.0MEMORY_LIMIT=128mNODE_HOME=/home/vcap/deps/0/node_=/home/vcap/deps/0/bin/npm

改行が入ってなくて見づらいので,NULL 文字を改行で置換:

$ cat /proc/14/environ | sed -e 's/\x0/\n/g'
CF_INSTANCE_ADDR=10.0.16.22:62543
TMPDIR=/home/vcap/tmp
WEB_MEMORY=512
MEMORY_AVAILABLE=128
VCAP_APP_PORT=8080
USER=vcap
CF_INSTANCE_INTERNAL_IP=10.254.0.2
VCAP_APPLICATION={"application_id":"a5b6ab92-90b8-401f-b3b8-c51ebee8ef52","application_name":"nodetask","application_uris":["nodetask.example.org"],"application_version":"e4551ace-13c3-4a15-942b-3d268efad711","cf_api":"https://api.example.org","host":"0.0.0.0","instance_id":"6ddbf04f-b643-4d87-7844-9fc6","instance_index":0,"limits":{"disk":128,"fds":16384,"mem":128},"name":"nodetask","port":8080,"space_id":"930a3384-7b8c-4262-a083-8cc11db48f0f","space_name":"example-spc","uris":["nodetask.example.org"],"version":"e4551ace-13c3-4a15-942b-3d268efad711"}
CF_INSTANCE_GUID=6ddbf04f-b643-4d87-7844-9fc6
PATH=/home/vcap/deps/0/bin:/usr/local/bin:/usr/bin:/bin:/home/vcap/app/bin:/home/vcap/app/node_modules/.bin
WEB_CONCURRENCY=1
PWD=/home/vcap/app
LANG=en_US.UTF-8
MY_ENV=my-value
CF_INSTANCE_PORT=62543
NODE_ENV=production
VCAP_SERVICES={}
CF_INSTANCE_IP=10.0.16.22
CF_INSTANCE_INDEX=0
HOME=/home/vcap/app
SHLVL=1
DEPS_DIR=/home/vcap/deps
CF_INSTANCE_PORTS=[{"external":62543,"internal":8080},{"external":62544,"internal":2222}]
INSTANCE_INDEX=0
PORT=8080
INSTANCE_GUID=6ddbf04f-b643-4d87-7844-9fc6
VCAP_APP_HOST=0.0.0.0
MEMORY_LIMIT=128m
NODE_HOME=/home/vcap/deps/0/node
_=/home/vcap/deps/0/bin/npm

ちなみに, env で得られた結果と, cat /proc/14/environ で得られた結果の差分は,以下のようになっている:

diff --git env.sorted.txt proc-environ.sorted.txt
index 4d931bb..34ffbbe 100644
--- env.sorted.txt
+++ proc-environ.sorted.txt
@@ -5,23 +5,26 @@ CF_INSTANCE_INTERNAL_IP=10.254.0.2
 CF_INSTANCE_IP=10.0.16.22
 CF_INSTANCE_PORT=62543
 CF_INSTANCE_PORTS=[{"external":62543,"internal":8080},{"external":62544,"internal":2222}]
-HOME=/home/vcap
+DEPS_DIR=/home/vcap/deps
+HOME=/home/vcap/app
 INSTANCE_GUID=6ddbf04f-b643-4d87-7844-9fc6
 INSTANCE_INDEX=0
 LANG=en_US.UTF-8
-LESSCLOSE=/bin/lesspipe %s %s
-LESSOPEN=| /bin/lesspipe %s
-LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:
+MEMORY_AVAILABLE=128
 MEMORY_LIMIT=128m
 MY_ENV=my-value
-PATH=/bin:/usr/bin
+NODE_ENV=production
+NODE_HOME=/home/vcap/deps/0/node
+PATH=/home/vcap/deps/0/bin:/usr/local/bin:/usr/bin:/bin:/home/vcap/app/bin:/home/vcap/app/node_modules/.bin
 PORT=8080
-PWD=/home/vcap
+PWD=/home/vcap/app
 SHLVL=1
-TERM=xterm
+TMPDIR=/home/vcap/tmp
 USER=vcap
-VCAP_APPLICATION={"cf_api":"https://api.example.org","limits":{"fds":16384,"mem":128,"disk":128},"application_name":"nodetask","application_uris":["nodetask.example.org"],"name":"nodetask","space_name":"example-spc","space_id":"930a3384-7b8c-4262-a083-8cc11db48f0f","uris":["nodetask.example.org"],"application_id":"a5b6ab92-90b8-401f-b3b8-c51ebee8ef52","version":"e4551ace-13c3-4a15-942b-3d268efad711","application_version":"e4551ace-13c3-4a15-942b-3d268efad711"}
+VCAP_APPLICATION={"application_id":"a5b6ab92-90b8-401f-b3b8-c51ebee8ef52","application_name":"nodetask","application_uris":["nodetask.example.org"],"application_version":"e4551ace-13c3-4a15-942b-3d268efad711","cf_api":"https://api.example.org","host":"0.0.0.0","instance_id":"6ddbf04f-b643-4d87-7844-9fc6","instance_index":0,"limits":{"disk":128,"fds":16384,"mem":128},"name":"nodetask","port":8080,"space_id":"930a3384-7b8c-4262-a083-8cc11db48f0f","space_name":"example-spc","uris":["nodetask.example.org"],"version":"e4551ace-13c3-4a15-942b-3d268efad711"}
 VCAP_APP_HOST=0.0.0.0
 VCAP_APP_PORT=8080
 VCAP_SERVICES={}
-_=/usr/bin/env
+WEB_CONCURRENCY=1
+WEB_MEMORY=512
+_=/home/vcap/deps/0/bin/npm

PATHHOME の値が異なり,また NODE_* のような Node.js 固有の環境変数が追加で定義されている点が主な差分と言えよう。

実は冒頭に書いたスクリプトは,この cat /proc/<PID>/environ 周りの手順を ある程度の確度で 自動化したものである。

ある程度の確度で と書いたのは,実はどのプロセスがアプリの起動プロセスかを決定する方法を確立できていない 1 ためである。

冒頭のスクリプトでいうと,

ps -ef | grep -v diego-sshd | awk ' $1 == "vcap" { print $0 } ' | awk ' $3 == 0 { print $2 } ' | sort -n | head -n 1

が「どのプロセスがアプリの起動プロセスか」を推定している部分にあたる。

順を追って説明する。

まず実行中のプロセスの一覧を取得:

$ ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 14:40 ?        00:00:00 /proc/self/exe init
vcap          13       0  0 14:40 ?        00:00:00 /tmp/lifecycle/diego-sshd --allowedKeyExchanges= --address=0.0.0.0:2222 --allowUnauthenticatedClients=false
vcap          14       0  0 14:40 ?        00:00:00 npm
vcap          66      14  0 14:40 ?        00:00:00 sh -c node server.js
vcap          67      66  0 14:40 ?        00:00:00 node server.js
vcap         124      13  0 14:42 pts/0    00:00:00 /bin/bash
vcap         421     124  0 14:56 pts/0    00:00:00 ps -ef

そこから diego-sshd のプロセスを除外:

$ ps -ef | grep -v diego-sshd
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 14:40 ?        00:00:00 /proc/self/exe init
vcap          14       0  0 14:40 ?        00:00:00 npm
vcap          66      14  0 14:40 ?        00:00:00 sh -c node server.js
vcap          67      66  0 14:40 ?        00:00:00 node server.js
vcap         124      13  0 14:42 pts/0    00:00:00 /bin/bash
vcap         516     124  0 15:01 pts/0    00:00:00 ps -ef

その中から,ユーザー vcap によって実行されているものを取得:

$ ps -ef | grep -v diego-sshd | awk ' $1 == "vcap" { print $0 } '
vcap          14       0  0 14:40 ?        00:00:00 npm
vcap          66      14  0 14:40 ?        00:00:00 sh -c node server.js
vcap          67      66  0 14:40 ?        00:00:00 node server.js
vcap         124      13  0 14:42 pts/0    00:00:00 /bin/bash
vcap         528     124  0 15:01 pts/0    00:00:00 ps -ef
vcap         530     124  0 15:01 pts/0    00:00:00 awk  $1 == "vcap" { print $0 }

さらにそこから,親プロセスが存在しない(即ちPPIDが0の)プロセスのプロセス番号(PID)を取得:

$ ps -ef | grep -v diego-sshd | awk ' $1 == "vcap" { print $0 } ' | awk ' $3 == 0 { print $2 } '
14

このケースでは,この時点で1つに絞られたので以後の処理は冗長だが,他の buildpack の事例を踏まえて,さらに以下の2つの処理を行う。

得られたPIDリストを数値順にソート:

$ ps -ef | grep -v diego-sshd | awk ' $1 == "vcap" { print $0 } ' | awk ' $3 == 0 { print $2 } ' | sort -n
14

先頭の(=最も小さい)PIDを取得:

$ ps -ef | grep -v diego-sshd | awk ' $1 == "vcap" { print $0 } ' | awk ' $3 == 0 { print $2 } ' | sort -n | head -n 1
14

こうして得たPIDのプロセスをアプリの起動プロセスとみなして,その環境変数を取得しているのが冒頭のスクリプトで,今の所これでうまくいっている。

$ cat /proc/$(ps -ef | grep -v diego-sshd | awk ' $1 == "vcap" { print $0 } ' | awk ' $3 == 0 { print $2 } ' | sort -n | head -n 1)/environ | sed -e 's/\x0/\n/g'
CF_INSTANCE_ADDR=10.0.16.22:62543
TMPDIR=/home/vcap/tmp
WEB_MEMORY=512
MEMORY_AVAILABLE=128
VCAP_APP_PORT=8080
USER=vcap
CF_INSTANCE_INTERNAL_IP=10.254.0.2
VCAP_APPLICATION={"application_id":"a5b6ab92-90b8-401f-b3b8-c51ebee8ef52","application_name":"nodetask","application_uris":["nodetask.example.org"],"application_version":"e4551ace-13c3-4a15-942b-3d268efad711","cf_api":"https://api.example.org","host":"0.0.0.0","instance_id":"6ddbf04f-b643-4d87-7844-9fc6","instance_index":0,"limits":{"disk":128,"fds":16384,"mem":128},"name":"nodetask","port":8080,"space_id":"930a3384-7b8c-4262-a083-8cc11db48f0f","space_name":"example-spc","uris":["nodetask.example.org"],"version":"e4551ace-13c3-4a15-942b-3d268efad711"}
CF_INSTANCE_GUID=6ddbf04f-b643-4d87-7844-9fc6
PATH=/home/vcap/deps/0/bin:/usr/local/bin:/usr/bin:/bin:/home/vcap/app/bin:/home/vcap/app/node_modules/.bin
WEB_CONCURRENCY=1
PWD=/home/vcap/app
LANG=en_US.UTF-8
MY_ENV=my-value
CF_INSTANCE_PORT=62543
NODE_ENV=production
VCAP_SERVICES={}
CF_INSTANCE_IP=10.0.16.22
CF_INSTANCE_INDEX=0
HOME=/home/vcap/app
SHLVL=1
DEPS_DIR=/home/vcap/deps
CF_INSTANCE_PORTS=[{"external":62543,"internal":8080},{"external":62544,"internal":2222}]
INSTANCE_INDEX=0
PORT=8080
INSTANCE_GUID=6ddbf04f-b643-4d87-7844-9fc6
VCAP_APP_HOST=0.0.0.0
MEMORY_LIMIT=128m
NODE_HOME=/home/vcap/deps/0/node
_=/home/vcap/deps/0/bin/npm

しかし,上述の過程を読んでもらえればわかるように,これが100%確実かどうかは正直自信はない。

もしもっと確実な「アプリの起動プロセスのPID」の取得方法をご存知の方がいたら,ぜひ教えてほしい。

Footnotes

  1. アプリのコンテナーがDEA上で実行されていた時は, /home/vcap/run.pid というファイルにアプリの起動プロセスのPIDが書かれていたので,それを読めば済んだのだが,Diegoになってそのファイルは廃止されたので,若干面倒な方法で推定せざるをえなくなった。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment