zaki work log

作業ログやら生活ログやらなんやら

Helm chartの設定値のカスタマイズと確認

helm installでchartをカスタマイズするには、helm show valuesの出力をベースにYAMLを作成する。

helm.sh

ちなみにこのYAMLファイルの名称ってあるのかな。。
(K8sのリソースのYAMLファイルなら「マニフェスト」とか、Ansibleなら「プレイブック」みたいな。。)

環境

$ kubectl version --short
Client Version: v1.19.2
Server Version: v1.18.2
$ helm version --short
v3.3.4+ga61ce56

Helm chartを普通にデプロイ

[zaki@cloud-dev helm-sample]$ helm install mysql-sample stable/mysql --create-namespace -n db
NAME: mysql-sample
LAST DEPLOYED: Sat Oct 24 10:49:03 2020
NAMESPACE: db
STATUS: deployed
REVISION: 1
NOTES:
MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
mysql-sample.db.svc.cluster.local

To get your root password run:

    MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace db mysql-sample -o jsonpath="{.data.mysql-root-password}" | base64 --decode; echo)

To connect to your database:

1. Run an Ubuntu pod that you can use as a client:

    kubectl run -i --tty ubuntu --image=ubuntu:16.04 --restart=Never -- bash -il

2. Install the mysql client:

    $ apt-get update && apt-get install mysql-client -y

3. Connect using the mysql cli, then provide your password:
    $ mysql -h mysql-sample -p

To connect to your database directly from outside the K8s cluster:
    MYSQL_HOST=127.0.0.1
    MYSQL_PORT=3306

    # Execute the following command to route the connection:
    kubectl port-forward svc/mysql-sample 3306

    mysql -h ${MYSQL_HOST} -P${MYSQL_PORT} -u root -p${MYSQL_ROOT_PASSWORD}

実は--create-namespaceって知らなかった。便利ね。

[zaki@cloud-dev helm-sample]$ kubectl get ns db
NAME   STATUS   AGE
db     Active   29s
[zaki@cloud-dev helm-sample]$ kubectl get pod -n db
NAME                            READY   STATUS    RESTARTS   AGE
mysql-sample-7d54bdc95f-dvrnx   1/1     Running   0          32s
[zaki@cloud-dev helm-sample]$ helm ls -n db
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
mysql-sample    db              1               2020-10-24 10:49:03.009404136 +0900 JST deployed        mysql-1.6.7     5.7.30     

この通り、podがデプロイされた。

chartのカスタマイズ

helm show values <chart name>で、デフォルト値を参照できるので、ファイルに出力する。

$ helm show values stable/mysql > mysql-conf/values.yaml

YAMLファイルの修正

metrics用のexporterがdisableになってるのでenableにしてみよう。

$ diff -u values.yaml.org values.yaml
--- values.yaml.org     2020-10-23 09:43:57.874016590 +0900
+++ values.yaml 2020-10-23 09:45:34.736721985 +0900
@@ -162,7 +162,7 @@
     enabled: false
 
 metrics:
-  enabled: false
+  enabled: true
   image: prom/mysqld-exporter
   imageTag: v0.10.0
   imagePullPolicy: IfNotPresent

helm upgradeで更新

[zaki@cloud-dev helm-sample]$ helm upgrade mysql-sample stable/mysql -n db -f mysql-conf/values.yaml
Release "mysql-sample" has been upgraded. Happy Helming!
NAME: mysql-sample
LAST DEPLOYED: Sat Oct 24 11:00:17 2020
NAMESPACE: db
STATUS: deployed
REVISION: 2
NOTES:
MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
mysql-sample.db.svc.cluster.local

To get your root password run:

    MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace db mysql-sample -o jsonpath="{.data.mysql-root-password}" | base64 --decode; echo)

To connect to your database:

1. Run an Ubuntu pod that you can use as a client:

    kubectl run -i --tty ubuntu --image=ubuntu:16.04 --restart=Never -- bash -il

2. Install the mysql client:

    $ apt-get update && apt-get install mysql-client -y

3. Connect using the mysql cli, then provide your password:
    $ mysql -h mysql-sample -p

To connect to your database directly from outside the K8s cluster:
    MYSQL_HOST=127.0.0.1
    MYSQL_PORT=3306

    # Execute the following command to route the connection:
    kubectl port-forward svc/mysql-sample 3306

    mysql -h ${MYSQL_HOST} -P${MYSQL_PORT} -u root -p${MYSQL_ROOT_PASSWORD}
[zaki@cloud-dev helm-sample]$ helm ls -n db
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
mysql-sample    db              2               2020-10-24 11:00:17.611382551 +0900 JST deployed        mysql-1.6.7     5.7.30     
[zaki@cloud-dev helm-sample]$ kubectl get pod -n db
NAME                            READY   STATUS    RESTARTS   AGE
mysql-sample-78889784cb-pdn7z   0/2     Running   0          3s
[zaki@cloud-dev helm-sample]$ kubectl get pod -n db
NAME                            READY   STATUS    RESTARTS   AGE
mysql-sample-78889784cb-pdn7z   2/2     Running   0          17s

podは再起動されて設定反映され、exporter分のコンテナが追加され2/2となっている。
コンテナ一覧は以下の通り

[zaki@cloud-dev helm-sample]$ kubectl get pod -n db mysql-sample-78889784cb-pdn7z -o jsonpath='{.spec.containers[*].image}{"\n"}'
mysql:5.7.30 prom/mysqld-exporter:v0.10.0

最小限のカスタマイズ

上記のvalues.yamlは「全ての設定情報が記載されてるファイルに必要な個所だけ変更」した状態だったが、「必要な設定のみ記載」したvalues.yamlでお試し。

分かりやすくするため、一旦まっさらな状態にする。

[zaki@cloud-dev helm-sample]$ helm uninstall -n db mysql-sample 
release "mysql-sample" uninstalled
[zaki@cloud-dev helm-sample]$ kubectl get pod -n db
NAME                            READY   STATUS        RESTARTS   AGE
mysql-sample-78889784cb-pdn7z   2/2     Terminating   0          23m

前述と同じ状態にするために以下のファイルを作成

[zaki@cloud-dev helm-sample]$ cat mysql-conf/values-metrics.yaml 
metrics:
  enabled: true
  image: prom/mysqld-exporter
  imageTag: v0.10.0
  imagePullPolicy: IfNotPresent
  resources: {}
  annotations: {}
    # prometheus.io/scrape: "true"
    # prometheus.io/port: "9104"
  livenessProbe:
    initialDelaySeconds: 15
    timeoutSeconds: 5
  readinessProbe:
    initialDelaySeconds: 5
    timeoutSeconds: 1
  flags: []
  serviceMonitor:
    enabled: false
    additionalLabels: {}
[zaki@cloud-dev helm-sample]$ helm install mysql-sample --create-namespace -n db stable/mysql -f mysql-conf/values-metrics.yaml 
NAME: mysql-sample
LAST DEPLOYED: Sat Oct 24 11:43:15 2020
NAMESPACE: db
STATUS: deployed
REVISION: 1
NOTES:
MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
mysql-sample.db.svc.cluster.local

To get your root password run:

    MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace db mysql-sample -o jsonpath="{.data.mysql-root-password}" | base64 --decode; echo)

To connect to your database:

1. Run an Ubuntu pod that you can use as a client:

    kubectl run -i --tty ubuntu --image=ubuntu:16.04 --restart=Never -- bash -il

2. Install the mysql client:

    $ apt-get update && apt-get install mysql-client -y

3. Connect using the mysql cli, then provide your password:
    $ mysql -h mysql-sample -p

To connect to your database directly from outside the K8s cluster:
    MYSQL_HOST=127.0.0.1
    MYSQL_PORT=3306

    # Execute the following command to route the connection:
    kubectl port-forward svc/mysql-sample 3306

    mysql -h ${MYSQL_HOST} -P${MYSQL_PORT} -u root -p${MYSQL_ROOT_PASSWORD}
[zaki@cloud-dev helm-sample]$ helm ls -n db
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
mysql-sample    db              1               2020-10-24 11:43:15.082173471 +0900 JST deployed        mysql-1.6.7     5.7.30     
[zaki@cloud-dev helm-sample]$ kubectl get pod -n db
NAME                            READY   STATUS    RESTARTS   AGE
mysql-sample-78889784cb-vm296   0/2     Running   0          5s

設定追加

さらに、元のvalues.yamlをベースに以下のファイル 追加で作成する。
(2か所あるfailureThresholdの値をどちらも3から10に変更している)

[zaki@cloud-dev helm-sample]$ cat mysql-conf/values-probe.yaml
livenessProbe:
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  successThreshold: 1
  failureThreshold: 10

readinessProbe:
  initialDelaySeconds: 5
  periodSeconds: 10
  timeoutSeconds: 1
  successThreshold: 1
  failureThreshold: 10

metrics設定のvalues-metrics.yamlと、ヘルスチェック設定のvalues-probe.yamlの二つのYAMLを指定してhelm upgradeする。

[zaki@cloud-dev helm-sample]$ helm upgrade mysql-sample -n db stable/mysql -f mysql-conf/values-metrics.yaml -f mysql-conf/values-probe.yaml
Release "mysql-sample" has been upgraded. Happy Helming!
NAME: mysql-sample
LAST DEPLOYED: Sat Oct 24 11:50:21 2020
NAMESPACE: db
STATUS: deployed
REVISION: 2
NOTES:
MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
mysql-sample.db.svc.cluster.local

To get your root password run:

    MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace db mysql-sample -o jsonpath="{.data.mysql-root-password}" | base64 --decode; echo)

To connect to your database:

1. Run an Ubuntu pod that you can use as a client:

    kubectl run -i --tty ubuntu --image=ubuntu:16.04 --restart=Never -- bash -il

2. Install the mysql client:

    $ apt-get update && apt-get install mysql-client -y

3. Connect using the mysql cli, then provide your password:
    $ mysql -h mysql-sample -p

To connect to your database directly from outside the K8s cluster:
    MYSQL_HOST=127.0.0.1
    MYSQL_PORT=3306

    # Execute the following command to route the connection:
    kubectl port-forward svc/mysql-sample 3306

    mysql -h ${MYSQL_HOST} -P${MYSQL_PORT} -u root -p${MYSQL_ROOT_PASSWORD}
[zaki@cloud-dev helm-sample]$ 
[zaki@cloud-dev helm-sample]$ 
[zaki@cloud-dev helm-sample]$ helm ls -n db
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
mysql-sample    db              2               2020-10-24 11:50:21.850677504 +0900 JST deployed        mysql-1.6.7     5.7.30     
[zaki@cloud-dev helm-sample]$ kubectl get pod -n db
NAME                            READY   STATUS        RESTARTS   AGE
mysql-sample-78889784cb-vm296   2/2     Terminating   0          7m15s
[zaki@cloud-dev helm-sample]$ kubectl get pod -n db
NAME                            READY   STATUS        RESTARTS   AGE
mysql-sample-78889784cb-vm296   0/2     Terminating   0          7m43s
[zaki@cloud-dev helm-sample]$ kubectl get pod -n db
NAME                            READY   STATUS            RESTARTS   AGE
mysql-sample-656fd547d4-4vddn   0/2     PodInitializing   0          2s
[zaki@cloud-dev helm-sample]$ kubectl get pod -n db
NAME                            READY   STATUS    RESTARTS   AGE
mysql-sample-656fd547d4-4vddn   2/2     Running   0          16s

-o yamlするとこんな感じにアップデートされる。

    livenessProbe:
      exec:
        command:
        - sh
        - -c
        - mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}
      failureThreshold: 10
      initialDelaySeconds: 30
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 5

    # ...

    readinessProbe:
      exec:
        command:
        - sh
        - -c
        - mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}
      failureThreshold: 10
      initialDelaySeconds: 5
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 1

さらにファイルを追加。

## Configure the service
## ref: http://kubernetes.io/docs/user-guide/services/
service:
  annotations: {}
  ## Specify a service type
  ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types
  type: NodePort
  port: 3306
  # nodePort: 32000
  # loadBalancerIP:

Serviceのtype定義があるので、NodePortに設定してみる。

[zaki@cloud-dev helm-sample]$ helm upgrade mysql-sample -n db stable/mysql -f mysql-conf/values-metrics.yaml -f mysql-conf/values-probe.yaml -f mysql-conf/values-service.yaml 
Release "mysql-sample" has been upgraded. Happy Helming!
NAME: mysql-sample
LAST DEPLOYED: Sat Oct 24 13:26:14 2020
NAMESPACE: db
STATUS: deployed
REVISION: 3

...
[zaki@cloud-dev helm-sample]$ kc get svc -n db
NAME           TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
mysql-sample   NodePort   10.103.97.209   <none>        3306:31559/TCP,9104:32084/TCP   103m

Serviceがtype:NodePortでデプロイされた。

ちなみにこのとき、probeの変更を行っているYAMLの指定を忘れると、元に戻される(デフォルトの設定でデプロイされる)ので注意。

[zaki@cloud-dev helm-sample]$ helm upgrade mysql-sample -n db stable/mysql -f mysql-conf/values-metrics.yaml -f mysql-conf/values-service.yaml Release "mysql-sample" has been upgraded. Happy Helming!
NAME: mysql-sample
LAST DEPLOYED: Sat Oct 24 13:27:31 2020
NAMESPACE: db
STATUS: deployed
REVISION: 4
NOTES:

...
[zaki@cloud-dev helm-sample]$ kc get pod -n db
NAME                            READY   STATUS        RESTARTS   AGE
mysql-sample-656fd547d4-4vddn   2/2     Terminating   0          96m
[zaki@cloud-dev helm-sample]$ kc get pod -n db
NAME                            READY   STATUS    RESTARTS   AGE
mysql-sample-78889784cb-mht58   2/2     Running   0          20s

現在の設定値を取得

chartをデプロイして実行中のreleaseから設定値を取得もできる。
(10/28 追記: -f values.yamlでデフォルトから変更している場合のみ。デフォルトでインストールしている場合はnullと出力される)

[zaki@cloud-dev helm-sample]$ helm ls -n db
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS  CHART           APP VERSION
mysql-sample    db              5               2020-10-24 13:29:18.169378549 +0900 JST failed  mysql-1.6.7     5.7.30     
[zaki@cloud-dev helm-sample]$ helm get values -n db mysql-sample
USER-SUPPLIED VALUES:
livenessProbe:
  failureThreshold: 10
  initialDelaySeconds: 30
  periodSeconds: 10
  successThreshold: 1
  timeoutSeconds: 5
metrics:
  annotations: {}
  enabled: true
  flags: []
  image: prom/mysqld-exporter
  imagePullPolicy: IfNotPresent
  imageTag: v0.10.0
  livenessProbe:
    initialDelaySeconds: 15
    timeoutSeconds: 5
  readinessProbe:
    initialDelaySeconds: 5
    timeoutSeconds: 1
  resources: {}
  serviceMonitor:
    additionalLabels: {}
    enabled: false
readinessProbe:
  failureThreshold: 10
  initialDelaySeconds: 5
  periodSeconds: 10
  successThreshold: 1
  timeoutSeconds: 1
service:
  annotations: {}
  port: 3306
  type: NodePort

ヘッダ行(USER-SUPPLIED VALUES:の部分)が入ってるので、設定ファイル用にYAMLで取得するなら-o yamlを付与すると無難。

ちなみにデフォルト値含めた全設定値は-aを付与すればダンプされる。

Ansibleのk8sモジュールでKubernetesクラスタ上のリソースを操作する

〇 2021.02.20: APIトークンを指定した方法について追記

Ansibleのk8sモジュールを使って、Kubernetes上のリソースを操作してみる。
意外といままで試してなかった…というか実は5月頃に途中まで試したけど当時はPython2環境でpipから入れたりして文書化に手間取ってお蔵入りしてた内容。

docs.ansible.com

環境

pip install ansibleしたAnsible 2.10環境

$ ansible --version
ansible 2.10.2
  config file = /home/zaki/src/ansible-sample/k8s/ansible.cfg
  configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/zaki/src/ansible-sample/venv/lib64/python3.6/site-packages/ansible
  executable location = /home/zaki/src/ansible-sample/venv/bin/ansible
  python version = 3.6.8 (default, Apr  2 2020, 13:34:55) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]

KubernetesはローカルのDocker上に立てたkindKubernetes v1.18クラスタ

$ kubeconfig get node -o wide
NAME                         STATUS   ROLES    AGE    VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE       KERNEL-VERSION                CONTAINER-RUNTIME
helm-cluster-control-plane   Ready    master   7d1h   v1.18.2   172.20.0.2    <none>        Ubuntu 19.10   3.10.0-1127.19.1.el7.x86_64   containerd://1.3.3-14-g449e9269
helm-cluster-worker          Ready    <none>   7d1h   v1.18.2   172.20.0.5    <none>        Ubuntu 19.10   3.10.0-1127.19.1.el7.x86_64   containerd://1.3.3-14-g449e9269
helm-cluster-worker2         Ready    <none>   7d1h   v1.18.2   172.20.0.4    <none>        Ubuntu 19.10   3.10.0-1127.19.1.el7.x86_64   containerd://1.3.3-14-g449e9269

(pip) openshiftパッケージ

openshift(>=0.6)が必要なので入れる。

$ pip install openshift

これが無いとエラーになる。

k8sモジュール

Kubernetes認証情報有り環境

まずは、kubectl create namespaceとか実行できるユーザー&Kubernetes設定で、Ansibleも実行できるという条件で以下実行。
(ansible-playbookを実行するユーザーの環境変数$KUBECONFIG~/.kube/configの設定内容でKubernetesリソース操作が可能である、という条件)

namespace作成

- hosts: localhost
  gather_facts: no

  tasks:
  - name: Create a k8s namespace
    community.kubernetes.k8s:
      name: testing
      api_version: v1
      kind: Namespace
      state: present

このplaybookを使ってansible-playbookを実行。

(venv) [zaki@cloud-dev k8s]$ ansible-playbook playbook.yml 
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] **************************************************************

TASK [Create a k8s namespace] *************************************************
changed: [localhost]

PLAY RECAP ********************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

すると、namespaceが作成される。

(venv) [zaki@cloud-dev k8s]$ kubectl get ns testing
NAME      STATUS   AGE
testing   Active   12s

再度ansible-playbookを実行すると

TASK [Create a k8s namespace] *************************************************
ok: [localhost]

ちゃんとokになる。

要は、以下のマニフェストapplyしたのと同じ状態。

apiVersion: v1
kind: Namespace
metadata:
  name: testing

srcを使ってマニフェストファイル指定でdeployment

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: sample-http
  name: sample-http
  namespace: testing
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample-http
  template:
    metadata:
      labels:
        app: sample-http
    spec:
      containers:
      - image: httpd
        name: httpd

こんな内容のマニフェストファイルが既にある状態で、このファイルのパスを指定して以下のplaybookを作成。
(前述のnamespace作成のplaybookに追記)

  - name: Create a Deployment by reading the definition from a local file
    community.kubernetes.k8s:
      state: present
      src: ./deployment.yaml
(venv) [zaki@cloud-dev k8s]$ ansible-playbook playbook.yml 
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] **************************************************************

TASK [Create a k8s namespace] *************************************************
ok: [localhost]

TASK [Create a Deployment by reading the definition from a local file] ********
changed: [localhost]

PLAY RECAP ********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

これでDeploymentリソースが作られて(そこから更にReplicaSetが作られて)Podがデプロイされる。

(venv) [zaki@cloud-dev k8s]$ kubectl get pod -n testing 
NAME                           READY   STATUS    RESTARTS   AGE
sample-http-744f56bdc6-fgrdm   1/1     Running   0          9s
sample-http-744f56bdc6-m6qv5   1/1     Running   0          9s

動いたね。
再実行すると、ちゃんとokになります。

(追記)
上記の例はマニフェスト内にnamespaceを指定してるけど、マニフェストからはnamespace指定を無くし、k8sモジュールのnamespaceパラメタをplaybookに記述しても動く。

マニフェスト

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: sample-http
  name: sample-http
spec:
  replicas: 2

# ...

playbook

    community.kubernetes.k8s:
      state: present
      namespace: testing
      src: ./deployment.yaml

Jinja2テンプレートでマニフェスト生成

上記の例だと、例えばネームスペースは可変にしたいよなーとかあるかもしれません。
そういう既存マニフェストをそのままでなく、テンプレート的に使うには、AnsibleでおなじみJinja2が使えます。
k8sモジュールだとtemplateを使用。

deployment.yaml.j2というファイル名で、下記マニフェストファイルのj2テンプレートファイルを用意。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: sample-http
  name: {{resourcename}}
  namespace: {{namespace}}
spec:
  replicas: {{replica}}
  selector:
    matchLabels:
      app: sample-http
  template:
    metadata:
      labels:
        app: sample-http
    spec:
      containers:
      - image: httpd
        name: httpd

Ansible用マニフェスト

  - name: Read definition template file from the Ansible controller file system
    vars:
      resourcename: tmpl-sample
      namespace: testing
      replica: 1
    community.kubernetes.k8s:
      state: present
      template: './deployment.yaml.j2'

このplaybookでansible-playbookを実行すると、tmpl-sampleという名前でDeploymentが作成されるので、Podの状態も以下の通りになる。

(venv) [zaki@cloud-dev k8s]$ kubectl get pod -n testing 
NAME                           READY   STATUS    RESTARTS   AGE
sample-http-744f56bdc6-fgrdm   1/1     Running   0          23m
sample-http-744f56bdc6-m6qv5   1/1     Running   0          23m
tmpl-sample-744f56bdc6-7nklj   1/1     Running   0          5s

resource_definitionでインライン定義

  - name: Create a Service object from an inline definition
    vars:
      namespace: testing
    community.kubernetes.k8s:
      state: present
      definition:
        apiVersion: v1
        kind: Service
        metadata:
          labels:
            app: sample-http
          name: sample-http
          namespace: "{{ namespace }}"
        spec:
          ports:
          - port: 80
            protocol: TCP
            targetPort: 80
            name: http
          selector:
            app: sample-http
          type: NodePort

マニフェストYAMLの内容をそのままPlaybookに書けばOK
Playbook内なので"{{ foobarbaz }}"というお馴染みの形式で変数参照も可能。

これでansible-playbookを実行すると

TASK [Create a Service object from an inline definition] **********************
changed: [localhost]

PLAY RECAP ********************************************************************
localhost                  : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

この通り、Serviceリソースが作成できる。

(venv) [zaki@cloud-dev k8s]$ kubectl get svc -n testing 
NAME          TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
sample-http   NodePort   10.110.123.20   <none>        80:30965/TCP   6s

認証用kubeconfigの指定

CI/CDとか。
とりあえず、~/.kubeとか$KUBECONFIGとか設定されていないすっぴんOSユーザーを作って、そこで実行するための設定

[root@cloud-dev ~]# useradd -m sample-user
[root@cloud-dev ~]# passwd sample-user

要はこの状態のsample-userユーザーで前述のPlaybookと同じものを実行。

TASK [Create a k8s namespace] *************************************************
fatal: [localhost]: FAILED! => changed=false 
  msg: Failed to load kubeconfig due to Invalid kube-config file. No configuration found.

ただ実行するとこの通り。
Kubernetesクラスター情報が何もないので。

kubeconfigファイル指定

例えばkindであれば、kind get kubeconfig --name <kindクラスター名> > kubeconfig.ymlを実行すれば、認証情報であるkubeconfigファイルを生成できる。
このファイルを指定すれば.kube/config環境変数$KUBECONFIGが無くても動作する。

  tasks:
  - name: Create a k8s namespace
    community.kubernetes.k8s:
      name: testing
      api_version: v1
      kind: Namespace
      state: present
      kubeconfig: /home/sample-user/kubeconfig.yml

こんな感じ。

APIトークン指定

kubeconfig指定でなく、ca_certclient_certを個別に指定して動かせないか試したけど、ちょっとよくわからなかった。(実現可能かどうかも不明)

可能。
下記2つのパラメタを使用する。

  • api_key: Secretのtokenの値
  • ca_cert: Secretのca.crtの内容が保存されたファイルのパス

必要な準備の大まかな流れは、処理用のアカウント(通常はServiceAccountがよいと思う)を作成し、このアカウントに対して適切な(Ansibleで操作する内容に必要な)RoleBindigを行い、そのSecretトークンを使用する。

ServiceAccount作成

マニフェストで作ってもよいし、kubectlで作ってもよい。

$ kubectl create serviceaccount sample-account

Roleの作成

$ kubectl create role sample-role --verb=* --resource=*

※ これで作成するとapiGroupsが空文字1つを要素にもつRoleが作成されるが、この状態のRoleだとデプロイが成功しないため、kubectl edit"*"に変更した。

rules:
- apiGroups:
  - ""
  resources:
  - '*'
  verbs:
  - '*'

↑これを↓こうする

rules:
- apiGroups:
  - '*'           # <- ここ
  resources:
  - '*'
  verbs:
  - '*'

RoleBindig

作成したServiceAccountに作成したRoleを割り当てる。

$ kubectl create rolebinding sample-rolebinding --role=sample-role --serviceaccount=rbac-sample:sample-account

APIトークンの確認

Roleが設定されたServiceAccountのトークンを使用する。
トークンはSecretリソースに保持されている。

$ kubectl get secret   # secretリソース名を確認
$ kubectl get secret (ServiceAccount名)-token-(ランダム文字列) -o jsonpath='{.data.token}' | base64 -d

証明書の確認

トークンと同じ要領で確認。
ただしAnsibleのk8sモジュールからはファイルのパスで指定する必要があるので、リダイレクトでファイル出力する。

$ kubectl get secret (ServiceAccount名)-token-(ランダム文字列) -o jsonpath='{.data.ca\.crt}' | base64 -d > data/ca_crt.crt

absentで削除

ここまでstate: presentで試していたけど、absentを指定すれば削除動作になる。

- hosts: localhost
  gather_facts: no

  tasks:
  - name: delete namespace
    community.kubernetes.k8s:
      name: testing
      api_version: v1
      kind: Namespace
      state: absent

このPlaybookでansible-playbookを実行すれば

(venv) [zaki@cloud-dev k8s]$ kubectl get ns testing 
NAME      STATUS        AGE
testing   Terminating   129m

この通り削除実行が動き出す。


サンプルコード

github.com

PrometheusのBlackbox Exporterを使ったターゲットノードの死活チェックをお試し (HTTP / TCP)

Blackbox Exporterをお試し。
機能的にはKubernetesのヘルスチェックのhttpGetおよびtcpSocketのように、外部からターゲットに対してネットワーク的に疎通確認を行うExporterとなっている。
(よって、監視対象ノード上で動かす必要はなく、Exporter(を動かすノード)から監視対象ノードへHTTPやTCPアクセスして死活監視を行うように動作する)

github.com

環境は例によってCentOS上に直接立てたPrometheus(非Kubernetes環境)

zaki-hmkc.hatenablog.com

実行バイナリの取得

2020.10.20時点でver 0.18.0

[zaki@cloud-dev prometheus]$ curl -LO https://github.com/prometheus/blackbox_exporter/releases/download/v0.18.0/blackbox_exporter-0.18.0.linux-amd64.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   667  100   667    0     0   1128      0 --:--:-- --:--:-- --:--:--  1130
100 8675k  100 8675k    0     0  2277k      0  0:00:03  0:00:03 --:--:-- 2706k
[zaki@cloud-dev prometheus]$ 
[zaki@cloud-dev prometheus]$ tar xf blackbox_exporter-0.18.0.linux-amd64.tar.gz 
[zaki@cloud-dev prometheus]$ ls -l blackbox_exporter-0.18.0.linux-amd64
合計 17860
-rw-r--r--. 1 zaki zaki    11357 10月 12 18:58 LICENSE
-rw-r--r--. 1 zaki zaki       94 10月 12 18:58 NOTICE
-rw-r--r--. 1 zaki zaki      629 10月 12 18:58 blackbox.yml
-rwxr-xr-x. 1 zaki zaki 18264924 10月 12 18:47 blackbox_exporter

Blackbox Exporterの実行

設定は付属のblackbox.ymlが使用できる。
起動オプション関連は--help付与すれば確認できる。

[zaki@cloud-dev prometheus]$ cd blackbox_exporter-0.18.0.linux-amd64/
[zaki@cloud-dev blackbox_exporter-0.18.0.linux-amd64]$ ./blackbox_exporter --config.file=blackbox.yml
level=info ts=2020-10-20T14:45:01.158Z caller=main.go:212 msg="Starting blackbox_exporter" version="(version=0.18.0, branch=HEAD, revision=60c86e6ce5a1111f7958b06ae7a08222bb6ec839)"
level=info ts=2020-10-20T14:45:01.158Z caller=main.go:213 msg="Build context" (gogo1.15.2,userroot@53d72328d93f,date20201012-09:46:31)=(MISSING)
level=info ts=2020-10-20T14:45:01.159Z caller=main.go:225 msg="Loaded config file"
level=info ts=2020-10-20T14:45:01.159Z caller=main.go:369 msg="Listening on address" address=:9115

デフォルトで9115/TCPでListenする。
この時点で9115/TCPへwebアクセスするとこんな感じ。

f:id:zaki-hmkc:20201020235041p:plain

/metricsにアクセスすれば、Prometheus用のメトリクス一覧も確認できる。

Prometheusへ設定追加

scrape_configsBlackbox Exporter用の設定を追加する。
params.moduleにHTTPアクセスチェック用のhttp_2xxを指定し、ターゲットとするwebサーバーを適当に用意し(なければGoogleYahoo!でも)、targetsのところへ設定する。
(http_2xxとかの定義は、前述のBlackbox Exporterの設定サンプルによるもの。)

Blackbox Exporterは192.168.0.18:9115で動作しているものとする。

設定自体はPrometheus Configurationを参照。

scrape_configs:

  # ...

  - job_name: 'blackbox-sample'
    metrics_path: /probe
    params:
      module: [http_2xx]  # Look for a HTTP 200 response.
    static_configs:
      - targets:
        - http://172.20.220.141
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: 192.168.0.18:9115  # The blackbox exporter's real hostname:port.

この設定でPrometheusを再起動する。

Targetを確認するとこんな感じ。

f:id:zaki-hmkc:20201020235540p:plain

メトリクス確認

probe_http_status_codeを確認すると200になっている。
(これをグラフで見ても無意味かな…w)

f:id:zaki-hmkc:20201020235649p:plain

また、probe_successを確認すると、1になっている。

f:id:zaki-hmkc:20201020235714p:plain

エラー発生させる

index.htmlを削除してもディレクトリ一覧表示になってしまうので、read権限を外した。

# chmod 600 index.html 

curlで確認すると403なのを確認。

[zaki@cloud-dev ~]$ curl http://172.20.220.141 
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
</body></html>

この状態でPrometheusから確認すると以下の通り。

f:id:zaki-hmkc:20201021000327p:plain

probe_successを確認すると、値は0となる。

ターゲット追加

HTTPS (insecure)

試しにオレオレ証明書httpsサイトを追加。
対象はローカルに立てているGitLabサーバー。

[zaki@cloud-dev ~]$ curl https://gitlab-ce.example.org:8443/ 
curl: (60) Peer's certificate issuer has been marked as not trusted by the user.
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

Prometheusのターゲット設定は以下の通り。

  - job_name: 'blackbox-sample'
    metrics_path: /probe
    params:
      module: [http_2xx]  # Look for a HTTP 200 response.
    static_configs:
      - targets:
        - http://172.20.220.141
        - https://gitlab-ce.example.org:8443/
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: 192.168.0.18:9115  # The blackbox exporter's real hostname:port.

これは証明書の検証は必要なようで、probe_success0となり、probe_http_status_code0となりHTTPのチェックとして成立しなかった。
が、これはHTTP Probeの設定で、insecureの設定を追加すれば証明書の検証をスキップできる。

以下は、既存のHTTP Probeに追加しているが、http_2xxとは別にmodule設定を(https_insecureとかの名前で)追加してもよいと思う。

modules:
  http_2xx:
    prober: http
    http:
      tls_config:
        insecure_skip_verify: true

TCP

Blackbox Exporterの標準のサンプル設定だとtcp_connectがそのまま使用できるので、(↑でinsecure設定はしているけど)ローカルGitLabのgitlab-ce.example.org:8443を監視設定に設定してみる。
以下のscrape設定をまるっと追加。

  - job_name: 'blackbox-tcpsample'
    metrics_path: /probe
    params:
      module: [tcp_connect]
    static_configs:
      - targets:
        - gitlab-ce.example.org:8443
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: 192.168.0.18:9115  # The blackbox exporter's real hostname:port.

f:id:zaki-hmkc:20201021002331p:plain


うーん、params.moduleはリスト形式で書けるけど、static_configs.targetsに書くターゲットには個別のモジュールは選択できそうにない。。

その他

Configuratoinのページを見る限り、HTTP・TCP以外に、ICMPとDNSのチェックができる模様。

参考

qiita.com

ものすごくざっくりとPrometheusのアラートルールを設定して動きを見てみる

Prometheusのアラートルールを設定する。
閾値を越えたらalert状態にするというもの。
この状態になった場合の通知はまた別途AlertManagerを使うが、その前段階の設定についてざっくりと確認してみた。

prometheus.io

環境やベースのPrometheusは以下の「CentOS上でバイナリ実行しているPrometheus」です。(非Kubernetes環境)

zaki-hmkc.hatenablog.com

alertルール作成

ルール設定用のファイルrules/alert-sample.ymlを作成する(ファイル名は任意)。
試しに、Node Exporterで取得できる値のうち、制御が簡単なnode_load1を対象に「値が0.8を超えたら」alertとなるように設定してみる。

groups:
- name: alert sample
  rules:
  - alert: load alert
    expr: node_load1 > 0.8
    for: 1m

こんな感じでexpr閾値を設定する。
また、その状態がどれだけ続いたら異常とするかの設定をforに記載する。(瞬間風速値だけで異常としないよう設定できる)

書式は分かりづらいけどAlerting RuleのページでなくRecording Ruleに載っている。

Prometheus設定と再起動

prometheus.ymlには以下の設定で読み込む設定にしている。

rule_files:
  - rules/*.yml

この状態でPrometheusを再起動すれば設定が有効になる。

$ ./prometheus --config.file=prometheus.yml

Alert設定状態

f:id:zaki-hmkc:20201019211614p:plain

webアクセスして「Alert」押下すると、設定したalertとその状態が確認できる。

異常状態になるように負荷をかける

お手軽負荷試験ツールyesを使用。(負荷試験ツールではない)

$ yes > /dev/null

PENDING

しばらくするとload averageが0.8を超えだす。

f:id:zaki-hmkc:20201019212147p:plain

まずは閾値を越えたタイミングで"PENDING"というstatusに遷移する。
Active Sinceは、その状態になったタイミングが記録されている。

ちなみにnode_load1のグラフはこんな感じ。

f:id:zaki-hmkc:20201019212847p:plain

FIRING

f:id:zaki-hmkc:20201019212520p:plain

PENDINGの状態がalertルールのfor:で設定した間(上記例で1min)継続すると、statusがFIRINGに遷移し、Prometheus的にこのメトリクス値が「異常」と判断される。

これでPrometheusのAlert画面を見ると、異常値があるかどうかを簡単に確認できる。

AlertManager

これで異常のハンドリングはできたけど、ユーザー(保守担当とか)がweb画面を見に行かないと異常はわからない。
Prometheus自体には異常時に通知する仕組みはないけど、AlertManagerを使って通知を行うことができる。

prometheus.io

これはまたそのうち。。

Helmチャートを使ったPrometheusのデプロイ (Prometheus Operator編)

(11/14 追記) 記事作成時よくわかってなかったけど、本記事では「Helmを使ってPrometheus Operatorをデプロイ、デプロイされたPrometheus OperatorがPrometheusやGrafanaをデプロイしている」という構成でした。
これを踏まえてタイトル含め少し加筆修正。

非Operator構成の場合は下記参照。

zaki-hmkc.hatenablog.com


Prometheus Operatorを使ったデプロイはマニフェストファイル使って簡単にできるけど、Helmチャート版もあるので試してみた。

github.com

環境

Helmはちょっと古い…
(現在の最新は3.3.4。な阪)

[zaki@cloud-dev ~]$ helm version --short
v3.2.4+g0ad800e

クラスターはkindを使って作ったばかりでまっさらなものを用意。

[zaki@cloud-dev ~]$ kubectl version --short
Client Version: v1.19.2
Server Version: v1.18.2
[zaki@cloud-dev ~]$ helm ls -A
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION

リポジトリ追加

$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

before

[zaki@cloud-dev ~]$ helm repo list
NAME    URL                                              
stable  https://kubernetes-charts.storage.googleapis.com/

追加

[zaki@cloud-dev ~]$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
"prometheus-community" has been added to your repositories
[zaki@cloud-dev ~]$ helm repo list
NAME                    URL                                               
stable                  https://kubernetes-charts.storage.googleapis.com/ 
prometheus-community    https://prometheus-community.github.io/helm-charts

チャートを検索

[zaki@cloud-dev ~]$ helm search repo prometheus-community
NAME                                                    CHART VERSION   APP VERSION     DESCRIPTION                                       
prometheus-community/alertmanager                       0.1.1           v0.21.0         The Alertmanager handles alerts sent by client ...
prometheus-community/kube-prometheus-stack              10.1.0          0.42.1          kube-prometheus-stack collects Kubernetes manif...
prometheus-community/prometheus                         11.16.2         2.21.0          Prometheus is a monitoring system and time seri...
prometheus-community/prometheus-adapter                 2.7.0           v0.7.0          A Helm chart for k8s prometheus adapter           
prometheus-community/prometheus-blackbox-exporter       4.7.0           0.17.0          Prometheus Blackbox Exporter                      
prometheus-community/prometheus-cloudwatch-expo...      0.9.0           0.8.0           A Helm chart for prometheus cloudwatch-exporter   
prometheus-community/prometheus-consul-exporter         0.2.0           0.4.0           A Helm chart for the Prometheus Consul Exporter   
prometheus-community/prometheus-couchdb-exporter        0.1.2           1.0             A Helm chart to export the metrics from couchdb...
prometheus-community/prometheus-druid-exporter          0.8.0           v0.8.0          Druid exporter to monitor druid metrics with Pr...
prometheus-community/prometheus-mongodb-exporter        2.8.1           v0.10.0         A Prometheus exporter for MongoDB metrics         
prometheus-community/prometheus-mysql-exporter          1.0.0           v0.12.1         A Helm chart for prometheus mysql exporter with...
prometheus-community/prometheus-nats-exporter           2.5.1           0.6.2           A Helm chart for prometheus-nats-exporter         
prometheus-community/prometheus-node-exporter           1.11.2          1.0.1           A Helm chart for prometheus node-exporter         
prometheus-community/prometheus-operator                9.3.2           0.38.1          DEPRECATED - This chart will be renamed. See ht...
prometheus-community/prometheus-postgres-exporter       1.3.3           0.8.0           A Helm chart for prometheus postgres-exporter     
prometheus-community/prometheus-pushgateway             1.4.2           1.2.0           A Helm chart for prometheus pushgateway           
prometheus-community/prometheus-rabbitmq-exporter       0.5.6           v0.29.0         Rabbitmq metrics exporter for prometheus          
prometheus-community/prometheus-redis-exporter          3.6.0           1.11.1          Prometheus exporter for Redis metrics             
prometheus-community/prometheus-snmp-exporter           0.0.6           0.14.0          Prometheus SNMP Exporter                          
prometheus-community/prometheus-to-sd                   0.3.1           0.5.2           Scrape metrics stored in prometheus format and ...

なるほど、色々ありますね。

で、ここからどれをインストールすればいいかは特に載ってないけど、prometheus-community/prometheus-operatorってあるからこれを入れれば楽なんだろ?と思ったんだけど、descriptionのとこ見たら思いっきり"DEPRECATED"と書いてあるのでスルー。

名前の感じからすると、prometheus-community/kube-prometheus-stackが良い感じに入りそう。
(11/14追記。このチャートをインストールすると、Prometheus Operatorがデプロイされる。prometheus-community/prometheusはPrometheus単体(依存でnode exporterなども入る)がインストールされる)

kube-prometheus-stack

github.com

Installs the kube-prometheus stack, a collection of Kubernetes manifests, Grafana dashboards, and Prometheus rules combined with documentation and scripts to provide easy to operate end-to-end Kubernetes cluster monitoring with Prometheus using the Prometheus Operator.

うん、オールインワンで入るって感じ。
というかこれの前身がprometheus-operatorとのこと。

リポジトリ追加

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add stable https://kubernetes-charts.storage.googleapis.com/

ドキュメントにはこの通り記載はあるけど、どちらもすでに実行済みなので省略。(stableはHelmインストール時のチュートリアルで100人中120人くらいはやってるはず)

helm repo update

あとはリポジトリをアップデートしておく。

[zaki@cloud-dev ~]$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "prometheus-community" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈ Happy Helming!⎈ 

チャートのインストール

ドキュメントには特にネームスペースの指定はないが、Operatorを使ったインストール時の構成に倣ってmonitoringネームスペースを作ってそこにデプロイしてみる。

[zaki@cloud-dev ~]$ kubectl create ns monitoring
namespace/monitoring created
[zaki@cloud-dev ~]$ helm install prometheus-stack prometheus-community/kube-prometheus-stack -n monitoring
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"

こんな感じでインストールが始まる。(そこそこ時間かかった)
別ターミナルでpodの状態を見るとこんな感じ。

[zaki@cloud-dev prometheus]$ kc get pod -n monitoring
NAME                                                     READY   STATUS              RESTARTS   AGE
alertmanager-prometheus-stack-kube-prom-alertmanager-0   0/2     ContainerCreating   0          70s
prometheus-prometheus-stack-kube-prom-prometheus-0       0/3     ContainerCreating   0          65s
prometheus-stack-grafana-67d44fddcd-l5cvz                0/2     PodInitializing     0          88s
prometheus-stack-kube-prom-admission-patch-vbnb4         0/1     Completed           0          83s
prometheus-stack-kube-prom-operator-88b8c6745-8ntq8      2/2     Running             0          88s
prometheus-stack-kube-state-metrics-bbdb95c45-vmh2s      1/1     Running             0          88s
prometheus-stack-prometheus-node-exporter-6rw25          1/1     Running             0          88s
prometheus-stack-prometheus-node-exporter-7jkb6          1/1     Running             0          88s
prometheus-stack-prometheus-node-exporter-nlgf2          1/1     Running             0          88s

もうしばらく待つと完了。
("skipping unknown hook"と出てるのは、、、なんだろう?)

[zaki@cloud-dev ~]$ helm install prometheus-stack prometheus-community/kube-prometheus-stack -n monitoring
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
NAME: prometheus-stack
LAST DEPLOYED: Thu Oct 15 23:36:19 2020
NAMESPACE: monitoring
STATUS: deployed
REVISION: 1
NOTES:
kube-prometheus-stack has been installed. Check its status by running:
  kubectl --namespace monitoring get pods -l "release=prometheus-stack"

Visit https://github.com/prometheus-operator/kube-prometheus for instructions on how to create & configure Alertmanager and Prometheus instances using the Operator.
[zaki@cloud-dev ~]$ helm ls -n monitoring
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                           APP VERSION
prometheus-stack        monitoring      1               2020-10-15 23:36:19.899109278 +0900 JST deployed        kube-prometheus-stack-10.1.0    0.42.1     
[zaki@cloud-dev ~]$ kc get pod,svc -n monitoring 
NAME                                                         READY   STATUS    RESTARTS   AGE
pod/alertmanager-prometheus-stack-kube-prom-alertmanager-0   2/2     Running   0          3m46s
pod/prometheus-prometheus-stack-kube-prom-prometheus-0       3/3     Running   1          3m41s
pod/prometheus-stack-grafana-67d44fddcd-l5cvz                2/2     Running   0          4m4s
pod/prometheus-stack-kube-prom-operator-88b8c6745-8ntq8      2/2     Running   0          4m4s
pod/prometheus-stack-kube-state-metrics-bbdb95c45-vmh2s      1/1     Running   0          4m4s
pod/prometheus-stack-prometheus-node-exporter-6rw25          1/1     Running   0          4m4s
pod/prometheus-stack-prometheus-node-exporter-7jkb6          1/1     Running   0          4m4s
pod/prometheus-stack-prometheus-node-exporter-nlgf2          1/1     Running   0          4m4s

NAME                                                TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
service/alertmanager-operated                       ClusterIP   None             <none>        9093/TCP,9094/TCP,9094/UDP   3m48s
service/prometheus-operated                         ClusterIP   None             <none>        9090/TCP                     3m41s
service/prometheus-stack-grafana                    ClusterIP   10.109.142.166   <none>        80/TCP                       4m4s
service/prometheus-stack-kube-prom-alertmanager     ClusterIP   10.108.73.186    <none>        9093/TCP                     4m5s
service/prometheus-stack-kube-prom-operator         ClusterIP   10.96.43.230     <none>        8080/TCP,443/TCP             4m4s
service/prometheus-stack-kube-prom-prometheus       ClusterIP   10.97.2.124      <none>        9090/TCP                     4m5s
service/prometheus-stack-kube-state-metrics         ClusterIP   10.99.165.64     <none>        8080/TCP                     4m5s
service/prometheus-stack-prometheus-node-exporter   ClusterIP   10.100.97.230    <none>        9100/TCP                     4m4s

デプロイが完了した。

webアクセス

Serviceの名前とポート番号から判断して、Prometheusのwebダッシュボードにアクセスするのはservice/prometheus-stack-kube-prom-prometheusぽいので、今回はNodePortに設定してアクセスしてみる。

[zaki@cloud-dev ~]$ kubectl patch svc -n monitoring prometheus-stack-kube-prom-prometheus -p '{"spec": {"type": "NodePort"}}'
service/prometheus-stack-kube-prom-prometheus patched
[zaki@cloud-dev ~]$ kc get svc -n monitoring prometheus-stack-kube-prom-prometheus 
NAME                                    TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
prometheus-stack-kube-prom-prometheus   NodePort   10.105.115.194   <none>        9090:32563/TCP   8m20s
[zaki@cloud-dev ~]$ kc get node -o wide
NAME                         STATUS   ROLES    AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE       KERNEL-VERSION                CONTAINER-RUNTIME
helm-cluster-control-plane   Ready    master   80m   v1.18.2   172.20.0.10   <none>        Ubuntu 19.10   3.10.0-1127.19.1.el7.x86_64   containerd://1.3.3-14-g449e9269
helm-cluster-worker          Ready    <none>   79m   v1.18.2   172.20.0.8    <none>        Ubuntu 19.10   3.10.0-1127.19.1.el7.x86_64   containerd://1.3.3-14-g449e9269
helm-cluster-worker2         Ready    <none>   79m   v1.18.2   172.20.0.9    <none>        Ubuntu 19.10   3.10.0-1127.19.1.el7.x86_64   containerd://1.3.3-14-g449e9269
[zaki@cloud-dev ~]$ curl 172.20.0.8:32563
<a href="/graph">Found</a>.

こんな感じ。

ブラウザでアクセスしてTargetsを確認するとこの通り。(***-stack-kube-***と見えてる)

f:id:zaki-hmkc:20201016000318p:plain

node_load1のグラフを出すとこんな感じ。

f:id:zaki-hmkc:20201016000541p:plain

Grafana

Prometheus本体と同じ要領でprometheus-stack-grafanaというServiceがあるので、これもNodePortでアクセスできるようにする。

[zaki@cloud-dev ~]$ kubectl patch svc -n monitoring prometheus-stack-grafana -p '{"spec": {"type": "NodePort"}}'
service/prometheus-stack-grafana patched
[zaki@cloud-dev ~]$ kc get svc -n monitoring prometheus-stack-grafana
NAME                       TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
prometheus-stack-grafana   NodePort   10.104.100.245   <none>        80:31929/TCP   18m

f:id:zaki-hmkc:20201016000923p:plain

Operatorでインストールした時と同じく、username: admin / password: admin で初期ログインできると思いきや、Invalid username or passwordに!!

secretにパスワードが入ってるのでそこからチェックすると

[zaki@cloud-dev ~]$ kc get secret -n monitoring prometheus-stack-grafana -o jsonpath='{.data.admin-password}' | base64 -d; echo
prom-operator
[zaki@cloud-dev ~]$ kc get secret -n monitoring prometheus-stack-grafana -o jsonpath='{.data.admin-user}' | base64 -d; echo
admin

という感じでログインできる。
どこでこれ定義しているかというと、ユーザー名については依存で入るGrafana本体のHelmチャートのvalues.yamlで、パスワードについてはkube-prometheus-stackのHelmチャートのvalues.yamlになっている。多分。

f:id:zaki-hmkc:20201016002409p:plain

metrics server

この時点ではまだメトリクスサーバーとして認識されていない

[zaki@cloud-dev ~]$ kubectl describe apiservice v1beta1.metrics.k8s.io
Error from server (NotFound): apiservices.apiregistration.k8s.io "v1beta1.metrics.k8s.io" not found

ので、topも使えない

[zaki@cloud-dev ~]$ kc top node
error: Metrics API not available

こちらはちょっと課題かな。

(残件?) unhealthyの項目

実はTargetsの一覧を見ると、一部のメトリクス取得に失敗しており、Healthyになっていない。
なんだろうな、これ。
未調査。(力尽きた)

f:id:zaki-hmkc:20201016003328p:plain

Prometheus OperatorでデプロイしたPrometheusにカスタムリソースを使ってレコードルール追加してみる

昨日の素のPrometheusでGetting Startedを通して動かしたことで、Node Exporterを使ったメトリクス値の収集と、ルールの追加時のファイル作成・prometheus.ymlへの設定追加を完全にマスターしたので、同じことをPrometheus OperatorでデプロイしたK8s上のPrometheusでやってみる。

参考記事

Operatorを使ってデプロイしたのでカスタムリソースで何とかなるだろうと辺りをつけつつ、かめねこさんのQiitaの記事を再度確認。

qiita.com

大体この辺りの機能で、やりたいことができそう。

  • ConfigReloader

    • PrometheusOperatorでは、単にPrometheusの展開だけではなく、設定の自動再読み込みもサポートしています

  • PrometheusRule

    • PrometheusRuleでは、PrometheusのRuleを設定することができます。

PrometheusRule

かめねこさんの記事の通り、PrometheusRuleというカスタムリソースが作成されている。

[zaki@cloud-dev ~]$ kubectl get prometheusrule -A
NAMESPACE    NAME                   AGE
monitoring   prometheus-k8s-rules   4d21h

kubectl get -o yamlで出力すればわかるけど、spec以下の内容は、Prometheusのrule_filesで読み込まれるルール設定が定義されている。

以下抜粋

spec:
  groups:
  - name: node-exporter.rules
    rules:
    - expr: |
        count without (cpu) (
          count without (mode) (
            node_cpu_seconds_total{job="node-exporter"}
          )
        )
      record: instance:node_num_cpu:sum
    - expr: |
        1 - avg without (cpu, mode) (
          rate(node_cpu_seconds_total{job="node-exporter", mode="idle"}[1m])
        )
      record: instance:node_cpu_utilisation:rate1m

      # ...

なので、このリソースに自分が必要なPrometheusルールの定義を追加してやればOK。

試しに、素のPrometheusをさわったときに作ったサンプルルールを追加してみる。
※ Node Exporterはこの通り標準で動作している。

[zaki@cloud-dev ~]$ kubectl get pod -n monitoring
NAME                                   READY   STATUS    RESTARTS   AGE
alertmanager-main-0                    2/2     Running   0          4d21h
alertmanager-main-1                    2/2     Running   0          4d21h
alertmanager-main-2                    2/2     Running   0          4d21h
grafana-86445dccbb-gx4p7               1/1     Running   0          4d21h
kube-state-metrics-5b67d79459-z4xh6    3/3     Running   0          4d21h
node-exporter-7rq9n                    2/2     Running   0          4d21h
node-exporter-cfxqr                    2/2     Running   0          4d21h
node-exporter-rzsh5                    2/2     Running   0          4d21h
prometheus-adapter-66b855f564-cst25    1/1     Running   0          4d21h
prometheus-k8s-0                       3/3     Running   1          4d21h
prometheus-k8s-1                       3/3     Running   1          4d21h
prometheus-operator-78fcb48ccf-zbx77   2/2     Running   0          4d21h

リソースの末尾に以下を追記

  - name: cpu-node
    rules:
    - record: job_instance_mode:node_cpu_seconds:avg_rate5m
      expr: avg by (job, instance, mode) (rate(node_cpu_seconds_total[5m]))

変更すると、ConfigMapはOperatorによって再作成される。

[zaki@cloud-dev ~]$ kc get cm -n monitoring  prometheus-k8s-rulefiles-0
NAME                         DATA   AGE
prometheus-k8s-rulefiles-0   1      7s

ConfigMapをVolumeMountしているPodは、通常であれば動的にPod内のファイルシステムへ反映されるが、Prometheusのプロセスは動的にファイルの状態を認識しないので本来はリスタートが必要。
ただしここでConfigReloaderの機能が効いてきて、PrometheusのPodを再作成することなく追加したルールが反映される。

f:id:zaki-hmkc:20201015215602p:plain

初期設定

ちなみに、初回デプロイ時から設定する場合は、たぶんこのYAMLに書いておけば良いはず。(未確認)

github.com

(参考) Prometheusの仕組みとPodの定義からみた設定情報

生のPrometheusだと、設定は--config.file=prometheus.ymlで実行時のオプションで指定していた。

Prometheus Operator環境だとどうなっているかと言うと、Podの定義(親リソースはStatefulSet)を見ると

  - args:
    - --web.console.templates=/etc/prometheus/consoles
    - --web.console.libraries=/etc/prometheus/console_libraries
    - --config.file=/etc/prometheus/config_out/prometheus.env.yaml

    [...]

となっており、Pod内の/etc/prometheus/config_out/prometheus.env.yamlにあるファイルの内容のルール関連は以下の通り。

rule_files:
- /etc/prometheus/rules/prometheus-k8s-rulefiles-0/*.yaml

じゃあこの/etc/prometheus/rules/prometheus-k8s-rulefiles-0/*.yamlはどうなってるかと言うと、Podの定義に戻ると、

    volumeMounts:

    # ...

    - mountPath: /etc/prometheus/rules/prometheus-k8s-rulefiles-0
      name: prometheus-k8s-rulefiles-0
  volumes:

  # ...

  - configMap:
      defaultMode: 420
      name: prometheus-k8s-rulefiles-0
    name: prometheus-k8s-rulefiles-0

こうなっていて、ConfigMapの定義をマウントしている。(mountPathはPrometheus本体と、rules-configmap-reloaderの両方にある)

じゃあConfigMapはどうなっているかと言うと、kubectl get configmaps -n monitoring prometheus-k8s-rulefiles-0 -o yamlを見ると、ズラーっとYAMLの定義を見ることができる。
ちなみにこのConfigMapリソース、よくよく見るとPrometheusというカスタムリソースが親リソースとして存在してReconciliation Loopで管理されてるので、直接変更はしないようにする。(というか、kubectl editとか使って変更しても、元に復元される)

  ownerReferences:
  - apiVersion: monitoring.coreos.com/v1
    blockOwnerDeletion: true
    controller: true
    kind: Prometheus
    name: k8s

PrometheusとNode ExporterをCentOS上でバイナリを直接動かして動きを確認してみる

先日のOperatorを使ったK8sクラスタへのデプロイは全自動だったので一体何が動いたのか!?という感じだったので、Prometheusのコンポーネントや動きを理解するために、素のPrometheusを使いGetting Startedの内容に沿って動かしてみる。

prometheus.io

環境はCentOS 7

$ cat /etc/redhat-release 
CentOS Linux release 7.8.2003 (Core)

Prometheus本体を動かす

Prometheusの入手

prometheus.io

2020.10.14時点で2.20のRC版がリリースされてるが、とりあえず2.21.0で。

$ curl -LO https://github.com/prometheus/prometheus/releases/download/v2.21.0/prometheus-2.21.0.linux-amd64.tar.gz
$ tar xf prometheus-2.21.0.linux-amd64.tar.gz

設定サンプル

アーカイブ内には、設定サンプル込みのpromehteus.ymlが含まれている。
サンプルの内容は、Prometheusサーバー自身のデータを収集する内容になっている。

# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

実行

prometheus実行バイナリを直接実行する。
実行時には設定ファイルを引数で指定。

[zaki@cloud-dev prometheus-2.21.0.linux-amd64]$ ls -F
LICENSE  NOTICE  console_libraries/  consoles/  prometheus*  prometheus.yml  promtool*
[zaki@cloud-dev prometheus-2.21.0.linux-amd64]$ ./prometheus --config.file=prometheus.yml
level=info ts=2020-10-14T12:07:52.694Z caller=main.go:310 msg="No time or size retention was set so using the default time retention" duration=15d
level=info ts=2020-10-14T12:07:52.694Z caller=main.go:346 msg="Starting Prometheus" version="(version=2.21.0, branch=HEAD, revision=e83ef207b6c2398919b69cd87d2693cfc2fb4127)"
level=info ts=2020-10-14T12:07:52.694Z caller=main.go:347 build_context="(go=go1.15.2, user=root@a4d9bea8479e, date=20200911-11:35:02)"
level=info ts=2020-10-14T12:07:52.696Z caller=main.go:348 host_details="(Linux 3.10.0-1127.19.1.el7.x86_64 #1 SMP Tue Aug 25 17:23:54 UTC 2020 x86_64 cloud-dev (none))"
level=info ts=2020-10-14T12:07:52.696Z caller=main.go:349 fd_limits="(soft=4096, hard=4096)"
level=info ts=2020-10-14T12:07:52.696Z caller=main.go:350 vm_limits="(soft=unlimited, hard=unlimited)"
level=info ts=2020-10-14T12:07:52.707Z caller=main.go:701 msg="Starting TSDB ..."
level=info ts=2020-10-14T12:07:52.709Z caller=web.go:523 component=web msg="Start listening for connections" address=0.0.0.0:9090
level=info ts=2020-10-14T12:07:52.715Z caller=head.go:644 component=tsdb msg="Replaying on-disk memory mappable chunks if any"
level=info ts=2020-10-14T12:07:52.715Z caller=head.go:658 component=tsdb msg="On-disk memory mappable chunks replay completed" duration=7.065µs
level=info ts=2020-10-14T12:07:52.715Z caller=head.go:664 component=tsdb msg="Replaying WAL, this may take a while"
level=info ts=2020-10-14T12:07:52.715Z caller=head.go:716 component=tsdb msg="WAL segment loaded" segment=0 maxSegment=0
level=info ts=2020-10-14T12:07:52.715Z caller=head.go:719 component=tsdb msg="WAL replay completed" checkpoint_replay_duration=22.965µs wal_replay_duration=313.668µs total_replay_duration=378.051µs
level=info ts=2020-10-14T12:07:52.717Z caller=main.go:721 fs_type=XFS_SUPER_MAGIC
level=info ts=2020-10-14T12:07:52.717Z caller=main.go:724 msg="TSDB started"
level=info ts=2020-10-14T12:07:52.717Z caller=main.go:850 msg="Loading configuration file" filename=prometheus.yml
level=info ts=2020-10-14T12:07:52.720Z caller=main.go:881 msg="Completed loading of configuration file" filename=prometheus.yml totalDuration=3.638807ms remote_storage=4.799µs web_handler=447ns query_engine=939ns scrape=3.217009ms scrape_sd=67.157µs notify=25.552µs notify_sd=19.669µs rules=1.612µs
level=info ts=2020-10-14T12:07:52.720Z caller=main.go:673 msg="Server is ready to receive web requests."

これで、ブラウザで9090/tcpにアクセスすればPrometheusのweb UIが表示される。

f:id:zaki-hmkc:20201014213145p:plain

※ firewalldが動いているなら9090/tcpを空ける

[zaki@cloud-dev ~]$ sudo firewall-cmd --add-port=9090/tcp
success

自身のメトリクス値

http://localhost:9090/metrics

ここにアクセスすると、実行したPrometheus自身の、Prometheusが値を取得しに来た時に返すメトリクス値の一覧が表示される。(ややこしいな) 言い換えると、Prometheusはターゲット(この場合は自分自身)の/metricsにアクセスしてメトリクスを収集する。

値のクエリー

例としてprometheus_target_interval_length_secondsの値を出してみる。

まずは「- insert metric at cursor」のドロップダウンメニューからprometheus_target_interval_length_secondsを選択する。

f:id:zaki-hmkc:20201014212241p:plain

選択したら"Execute"を押下。すると下部にqueryの結果が表示される。

f:id:zaki-hmkc:20201014212711p:plain

ちなみに http://localhost:9090/metrics にアクセスして表示されるメトリクス値は以下の通りなので、queryにヒットして表示された値と一致していることがわかる。

:
:

# HELP prometheus_target_interval_length_seconds Actual intervals between scrapes.
# TYPE prometheus_target_interval_length_seconds summary
prometheus_target_interval_length_seconds{interval="15s",quantile="0.01"} 14.99985301
prometheus_target_interval_length_seconds{interval="15s",quantile="0.05"} 14.999877942
prometheus_target_interval_length_seconds{interval="15s",quantile="0.5"} 15.000014192
prometheus_target_interval_length_seconds{interval="15s",quantile="0.9"} 15.00010798
prometheus_target_interval_length_seconds{interval="15s",quantile="0.99"} 15.000207268
prometheus_target_interval_length_seconds_sum{interval="15s"} 990.0011557570001
prometheus_target_interval_length_seconds_count{interval="15s"} 66

:
:

値の絞り込み

クエリーのprometheus_target_interval_length_secondsに条件を追加してみる。
具体的にはprometheus_target_interval_length_seconds{quantile="0.99"}と入力してExecute押下

すると、結果が絞り込まれる。

f:id:zaki-hmkc:20201014213929p:plain

※ ちょっとよくわかっていない。ざっくり検索した感じだと、PromQLを使って、ラベルの絞り込みをやってる…ぽい

グラフを表示

Graphタブを押下すれば、表示が切り替わる。

f:id:zaki-hmkc:20201014213657p:plain

Node Exporterでターゲットを追加する

ここまでは、Prometheus自身のメトリクスを表示していたが、Node Exporterを動かすことで、ターゲットを追加できる。

Starting up some sample targets

Node Exporterの入手

Prometheus本体には含まれていないので、別途ダウンロードする。

https://prometheus.io/download/#node_exporter

$ curl -LO https://github.com/prometheus/node_exporter/releases/download/v1.0.1/node_exporter-1.0.1.linux-amd64.tar.gz
$ tar xf node_exporter-1.0.1.linux-amd64.tar.gz 

Node Exporterを実行する

[zaki@cloud-dev node_exporter-1.0.1.linux-amd64]$ ls -F
LICENSE  NOTICE  node_exporter*

foregroundで動くので、ターミナル3つ起動して、3つのコマンドそれぞれ実行する。 (あと、リモートから見たいので、Listenのアドレスを0.0.0.0に変更してる)

[zaki@cloud-dev node_exporter-1.0.1.linux-amd64]$ ./node_exporter --web.listen-address 0.0.0.0:8080
level=info ts=2020-10-14T13:01:02.719Z caller=node_exporter.go:177 msg="Starting node_exporter" version="(version=1.0.1, branch=HEAD, revision=3715be6ae899f2a9b9dbfd9c39f3e09a7bd4559f)"
level=info ts=2020-10-14T13:01:02.719Z caller=node_exporter.go:178 msg="Build context" build_context="(go=go1.14.4, user=root@1f76dbbcfa55, date=20200616-12:44:12)"
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:105 msg="Enabled collectors"
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=arp
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=bcache
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=bonding
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=btrfs
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=conntrack
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=cpu
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=cpufreq
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=diskstats
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=edac
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=entropy
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=filefd
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=filesystem
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=hwmon
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=infiniband
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=ipvs
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=loadavg
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=mdadm
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=meminfo
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=netclass
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=netdev
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=netstat
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=nfs
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=nfsd
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=powersupplyclass
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=pressure
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=rapl
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=schedstat
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=sockstat
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=softnet
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=stat
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=textfile
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=thermal_zone
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=time
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=timex
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=udp_queues
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=uname
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=vmstat
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=xfs
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:112 collector=zfs
level=info ts=2020-10-14T13:01:02.720Z caller=node_exporter.go:191 msg="Listening on" address=0.0.0.0:8080
level=info ts=2020-10-14T13:01:02.720Z caller=tls_config.go:170 msg="TLS is disabled and it cannot be enabled on the fly." http2=false

同様に、8081/tcp, 8082/tcpでも別ターミナルで起動する。

これで、Prometheus本体のときと同じように、http://localhost:8080/metrics にアクセスすると、今起動したNode Exporterが提供するメトリクス値を確認できる。

Node Exporter追加分の設定を変更しPrometheus再起動

3つのメトリクスのエンドポイントを用意したので、prometheus.ymlに項目追加する。
既存のlocalhost:9090一つのリストに単純に追加してもいいが、Getting Startedの設定例にならって、グループ名も設定してみる。

  - job_name:       'node'
    scrape_interval: 5s
    static_configs:
    - targets: ['localhost:8080', 'localhost:8081']
      labels:
        group: 'production'

    - targets: ['localhost:8082']
      labels:
        group: 'canary'

PrometheusはCtrl-cで停止し、上記の内容に変更してPrometheusを再度起動。

メトリクス取得対象となっているターゲットは、web画面の「Targets」ページから確認できる。

f:id:zaki-hmkc:20201014222342p:plain

現在、デフォルトのPrometheus本体と、Node Exporterを使った3つのターゲットになっており、job_nameで設定した名前単位でリストされる。

f:id:zaki-hmkc:20201014231212p:plain

値のクエリー

この状態でnode_cpu_seconds_totalだけの値を出してみると、3つのNode Exporter全ての値(さらに環境によるがCPUコアごと…手元の環境だと4コアなのでcpu="0"からcpu="3"まで)が出力される。

prometheus.ymlで定義したグループの「production」だけに絞る場合はnode_cpu_seconds_total{group="production"}でクエリーすれば項目を絞れる。

ルールの作成

Configure rules for aggregating scraped data into new time series

複雑なルールや時系列の集約を都度実行すると負荷が高いため(←解釈間違ってるかもしれないけど)、Prometheusでは予め作成した式を用いて値を記録しておくことができる。(と書いてあるように読み取った)

5分間の平均CPU時間(でいい?)であれば

avg by (job, instance, mode) (rate(node_cpu_seconds_total[5m]))

というクエリーになる。

※ 実際にExecuteしてグラフ表示するとこうなった

f:id:zaki-hmkc:20201014223911p:plain

この結果の値をクエリーでなくメトリクス値として記録するには、ルールファイルを別途作成する。
作成するルールファイルprometheus.rules.ymlは以下の通り

groups:
- name: cpu-node
  rules:
  - record: job_instance_mode:node_cpu_seconds:avg_rate5m
    expr: avg by (job, instance, mode) (rate(node_cpu_seconds_total[5m]))

そして、メインの設定ファイルprometheus.ymlから、このルールファイルをincludeするように記述を追加する。
初期のサンプル設定ファイルだと、以下のコメントアウトされている部分。

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

これをこうじゃ

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  - "prometheus-rule.yml"

この設定変更を行い、Prometheusを再起動する。
すると、ドロップダウンメニューで選べるメトリクス種別の中にjob_instance_mode:node_cpu_seconds:avg_rate5mが追加され、これを選ぶだけで(式を評価した結果の)メトリクスを表示できる。

収集したメトリクス値

ちなみにPrometheusのバイナリのあるディレクトリはこの通り。

[zaki@cloud-dev prometheus-2.21.0.linux-amd64]$ ls -lF
合計 161132
-rw-r--r--. 1 zaki zaki    11357  9月 11 22:29 LICENSE
-rw-r--r--. 1 zaki zaki     3420  9月 11 22:29 NOTICE
drwxr-xr-x. 2 zaki zaki       38  9月 11 22:29 console_libraries/
drwxr-xr-x. 2 zaki zaki      173  9月 11 22:29 consoles/
drwxrwxr-x. 4 zaki zaki       70 10月 14 21:07 data/
-rwxr-xr-x. 1 zaki zaki 88471209  9月 11 20:37 prometheus*
-rw-rw-r--. 1 zaki zaki      166 10月 14 22:41 prometheus-rule.yml
-rw-r--r--. 1 zaki zaki     1184 10月 14 22:43 prometheus.yml
-rw-r--r--. 1 zaki zaki      926 10月 14 22:13 prometheus.yml.org
-rwxr-xr-x. 1 zaki zaki 76493104  9月 11 20:39 promtool*

data/以下にデータが出力される。

前述のように、収集済みメトリクス値からクエリーで出した値でなく、ルールを作成して新しいメトリクス値を作った場合は、作成した時点からの収集となるので、それ以前のデータは(メトリクス値としては)見えない (同じ式でクエリーを実行すればクエリーの結果としては見える)


参考: Dockerイメージ

ちなみにDockerイメージもあるので、簡単に試すこともできた。
prometheus.ymlを用意するようになってるけど、とりあえず空ファイルでも動作する。

$ touch /tmp/prometheus.yml
$ docker run \
    -p 9090:9090 \
    -v /tmp/prometheus.yml:/etc/prometheus/prometheus.yml \
    --rm \
    --name prometheus \
    prom/prometheus

実行例

zaki@cloud-dev ~]$ docker run \
>     -p 9090:9090 \
>     -v /tmp/prometheus.yml:/etc/prometheus/prometheus.yml \
>     --rm \
>     --name prometheus \
>     prom/prometheus
level=info ts=2020-10-14T11:44:44.529Z caller=main.go:310 msg="No time or size retention was set so using the default time retention" duration=15d
level=info ts=2020-10-14T11:44:44.529Z caller=main.go:346 msg="Starting Prometheus" version="(version=2.21.0, branch=HEAD, revision=e83ef207b6c2398919b69cd87d2693cfc2fb4127)"
level=info ts=2020-10-14T11:44:44.529Z caller=main.go:347 build_context="(go=go1.15.2, user=root@a4d9bea8479e, date=20200911-11:35:02)"
level=info ts=2020-10-14T11:44:44.529Z caller=main.go:348 host_details="(Linux 3.10.0-1127.19.1.el7.x86_64 #1 SMP Tue Aug 25 17:23:54 UTC 2020 x86_64 1951c31b3707 (none))"
level=info ts=2020-10-14T11:44:44.529Z caller=main.go:349 fd_limits="(soft=1048576, hard=1048576)"
level=info ts=2020-10-14T11:44:44.529Z caller=main.go:350 vm_limits="(soft=unlimited, hard=unlimited)"
level=info ts=2020-10-14T11:44:44.530Z caller=web.go:523 component=web msg="Start listening for connections" address=0.0.0.0:9090
level=info ts=2020-10-14T11:44:44.530Z caller=main.go:701 msg="Starting TSDB ..."
level=info ts=2020-10-14T11:44:44.535Z caller=head.go:644 component=tsdb msg="Replaying on-disk memory mappable chunks if any"
level=info ts=2020-10-14T11:44:44.535Z caller=head.go:658 component=tsdb msg="On-disk memory mappable chunks replay completed" duration=5.044µs
level=info ts=2020-10-14T11:44:44.535Z caller=head.go:664 component=tsdb msg="Replaying WAL, this may take a while"
level=info ts=2020-10-14T11:44:44.535Z caller=head.go:716 component=tsdb msg="WAL segment loaded" segment=0 maxSegment=0
level=info ts=2020-10-14T11:44:44.535Z caller=head.go:719 component=tsdb msg="WAL replay completed" checkpoint_replay_duration=31.707µs wal_replay_duration=184.249µs total_replay_duration=234.576µs
level=info ts=2020-10-14T11:44:44.536Z caller=main.go:721 fs_type=XFS_SUPER_MAGIC
level=info ts=2020-10-14T11:44:44.536Z caller=main.go:724 msg="TSDB started"
level=info ts=2020-10-14T11:44:44.536Z caller=main.go:850 msg="Loading configuration file" filename=/etc/prometheus/prometheus.yml
level=info ts=2020-10-14T11:44:44.536Z caller=main.go:881 msg="Completed loading of configuration file" filename=/etc/prometheus/prometheus.yml totalDuration=557.508µs remote_storage=3.834µs web_handler=392ns query_engine=939ns scrape=503.038µs scrape_sd=5.5µs notify=442ns notify_sd=953ns rules=741ns
level=info ts=2020-10-14T11:44:44.537Z caller=main.go:673 msg="Server is ready to receive web requests."

空設定だと動かないと思ってたのでちょっとビックリ笑

[zaki@cloud-dev ~]$ docker ps | head -2
CONTAINER ID        IMAGE                  COMMAND                  CREATED              STATUS               PORTS                       NAMES
1951c31b3707        prom/prometheus        "/bin/prometheus --c…"   About a minute ago   Up About a minute    0.0.0.0:9090->9090/tcp      prometheus

今回はNode Exporterを使う時にlocalhostでやろうとするとコンテナ間通信とか出てきて、コンテナネットワークの設定の要素も出てくるので、解説視点的にややこしくなるので一旦保留した。

まとめ

ここまでで確認した内容は以下の通り。
(ここで書いたのが全て、というわけじゃないです)

  • Prometheus本体
    • 対象のメトリクスの収集
    • メトリクスの保存
    • クエリーによるメトリクスの抽出
  • Node Exporter
    • 代表的なExporterの一つ (Node以外にいろいろある)
    • ノードのCPUやメモリ、ネットワークやストレージなどのメトリクスを収集
  • Exporter
    • Prometheusからのリクエストに対して収集したメトリクスを返す