[Prometheus] Postgres Exporter with Docker

## 関連
- [[目次] Prometheusで監視システムを作る with Docker](https://qiita.com/Esfahan/items/7dc780501f418a71ad9d)
- [DockerコンテナからホストOSのPostgresにアクセス全開設定にせずにログインする方法](https://qiita.com/Esfahan/items/47ade6398aa2f8a466b1)

## 概要
Prometheusでの、PostgreSQLのメトリクス収集用のPostgres ExporterのDocker環境構築手順。

## 前提条件
以下の手順でPrometheus環境を構築していることを前提とする。
https://qiita.com/Esfahan/items/0feaedfd771f49ac7ee4

## Postgres exporter
こちらを利用する。
https://github.com/prometheus-community/postgres_exporter

## 環境
- postgres_exporter v0.9.0
- PostgreSQL 9.6

## 監視対象のPostgresの設定

### pg_stat_statementsの有効化
以下を参考にpg_stat_statementsを有効化する。
参考：https://qiita.com/Esfahan/items/2f0c5ad85671d435f209

もしpg_stat_statementsを有効化しない場合は、次項の「メトリクス収集用ユーザーの作成」で、`CREATE OR REPLACE VIEW postgres_exporter.pg_stat_statements`の箇所を実行せずにskipする。

### メトリクス収集用ユーザーの作成
defaultでは like pg_stat_statements や pg_stat_activity はsuperuser以外では参照できない。もしsuperuser以外でメトリクス収集する場合は、以下を実行する。

以下の例では`postgres_exporter`という名前のメトリクス収集用のユーザーを作成する。
パスワードは、`ALTER USER postgres_exporter WITH PASSWORD 'password';`で設定しているので、パスワード値を適宜変更する。

参考：https://github.com/prometheus-community/postgres_exporter#running-as-non-superuser

```sql
-- To use IF statements, hence to be able to check if the user exists before
-- attempting creation, we need to switch to procedural SQL (PL/pgSQL)
-- instead of standard SQL.
-- More: https://www.postgresql.org/docs/9.3/plpgsql-overview.html
-- To preserve compatibility with <9.0, DO blocks are not used; instead,
-- a function is created and dropped.
CREATE OR REPLACE FUNCTION __tmp_create_user() returns void as $$
BEGIN
  IF NOT EXISTS (
          SELECT                       -- SELECT list can stay empty for this
          FROM   pg_catalog.pg_user
          WHERE  usename = 'postgres_exporter') THEN
    CREATE USER postgres_exporter;
  END IF;
END;
$$ language plpgsql;

SELECT __tmp_create_user();
DROP FUNCTION __tmp_create_user();

-- passwordを適宜変更
ALTER USER postgres_exporter WITH PASSWORD 'password';
ALTER USER postgres_exporter SET SEARCH_PATH TO postgres_exporter,pg_catalog;

-- If deploying as non-superuser (for example in AWS RDS), uncomment the GRANT
-- line below and replace <MASTER_USER> with your root user.
-- GRANT postgres_exporter TO <MASTER_USER>;
CREATE SCHEMA IF NOT EXISTS postgres_exporter;
GRANT USAGE ON SCHEMA postgres_exporter TO postgres_exporter;
GRANT CONNECT ON DATABASE postgres TO postgres_exporter;

CREATE OR REPLACE FUNCTION get_pg_stat_activity() RETURNS SETOF pg_stat_activity AS
$$ SELECT * FROM pg_catalog.pg_stat_activity; $$
LANGUAGE sql
VOLATILE
SECURITY DEFINER;

CREATE OR REPLACE VIEW postgres_exporter.pg_stat_activity
AS
  SELECT * from get_pg_stat_activity();

GRANT SELECT ON postgres_exporter.pg_stat_activity TO postgres_exporter;

CREATE OR REPLACE FUNCTION get_pg_stat_replication() RETURNS SETOF pg_stat_replication AS
$$ SELECT * FROM pg_catalog.pg_stat_replication; $$
LANGUAGE sql
VOLATILE
SECURITY DEFINER;

CREATE OR REPLACE VIEW postgres_exporter.pg_stat_replication
AS
  SELECT * FROM get_pg_stat_replication();

GRANT SELECT ON postgres_exporter.pg_stat_replication TO postgres_exporter;

CREATE OR REPLACE FUNCTION get_pg_stat_statements() RETURNS SETOF pg_stat_statements AS
$$ SELECT * FROM public.pg_stat_statements; $$
LANGUAGE sql
VOLATILE
SECURITY DEFINER;

-- pg_stat_statementsを利用しない場合は以降は実行しない
CREATE OR REPLACE VIEW postgres_exporter.pg_stat_statements
AS
  SELECT * FROM get_pg_stat_statements();

GRANT SELECT ON postgres_exporter.pg_stat_statements TO postgres_exporter;
```

### 作成されたものなど確認

```sql
psql -U postgres_exporter -W -d postgres
postgres=# \l
                                    List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |      Access privileges
-----------+----------+----------+------------+------------+------------------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =Tc/postgres                +
           |          |          |            |            | postgres=CTc/postgres       +
           |          |          |            |            | postgres_exporter=c/postgres
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres                 +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres                 +
           |          |          |            |            | postgres=CTc/postgres
(3 rows)

postgres=# \dn
       List of schemas
       Name        |  Owner
-------------------+----------
 postgres_exporter | postgres
 public            | postgres
(2 rows)

postgres=# \du
                                       List of roles
     Role name     |                         Attributes                         | Member of
-------------------+------------------------------------------------------------+-----------
 postgres          | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
 postgres_exporter |                                                            | {}

postgres=> \d
                     List of relations
      Schema       |        Name         | Type |  Owner
-------------------+---------------------+------+----------
 postgres_exporter | pg_stat_activity    | view | postgres
 postgres_exporter | pg_stat_replication | view | postgres
 postgres_exporter | pg_stat_statements  | view | postgres
(3 rows)
```

## Postgres Exporterの環境構築

```yaml:docker-compose.yaml
version: '3'

services:
  postgres-exporter:
    container_name: postgres-exporter
    image: quay.io/prometheuscommunity/postgres-exporter
    hostname: postgres-exporter
    env_file:
      - .env
    ports:
      - 9187:9187
    networks:
      - sample-network
networks:
    sample-network:
        external: true
```

```ini:.env
DATA_SOURCE_NAME=postgresql://postgres_exporter:password@sample-postgres:5432/postgres?sslmode=disable
```

DATA_SOURCE_NAMEは、`"`や`'`などクォーテーションを付けるとpostgres-exporterでパースされなかったので付けないこと。

```
$ docker-compose up -d --build
```

## Prometheusの設定
[こちらの記事](https://qiita.com/Esfahan/items/0feaedfd771f49ac7ee4)で構築したPrometheus環境の設定ファイルを編集する。

### 監視対象を追加
prometheus.yamlに以下を追記。

```diff:prometheus/prometheus.yaml
# 前略
scrape_configs:
+  - job_name: postgres
+    metrics_path: /postgres_exporter/metrics
+    static_configs:
+      - targets:
+        - your-postgres-exporter-hostname:9187
+        labels:
+          env: development
```

設定を反映

```console
$ sudo docker-compose restart prometheus
```

### ルールを追加

```yaml:/etc/prometheus/alert.rules
groups:
  - name: sample001
    rules:
      # Postgres用のルールを追記
      - alert: Postgres_down
        # Postgresが起動しているかのPromQL。
        expr: pg_up == 0
        # 5分間応答がなければアラートを飛ばす
        for: 5m
        labels:
          severity: critical
        # エラー文言
        annotations:
          firing_text: "[{{ $labels.env }}] {{ $labels.instance }} PostgreSQL has been down for more than 5 minutes."
          resolved_text: "[{{ $labels.env }}] {{ $labels.instance }} PostgreSQL has recoverd."
```

## UI確認
[http://your_prometeus.com:9090/graph](http://your_prometeus.com:9090/graph)
`pg_up == 1`であればpostgresが正常に起動しているということ。`0`が異常状態。
![Screen Shot 2021-06-13 at 16.04.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105936/00c4cc5e-8c61-c1dd-61fa-d00050d485eb.png)

## その他
### Error: function pg_current_wal_lsn() does not exist
`pg_current_wal_lsn()` はpostgres 10以降に実装された関数なので、9系ではエラーが出る。

```console
$ docker logs -f postgres-exporter
time="2021-06-07T17:51:16Z" level=info msg="Error running query on database \"sample-postgres:5432\": pg_replication_slots pq: function pg_current_wal_lsn() does not exist" source="postgres_exporter.go:1503"
```

2021/03/04にIssueが上がっており、対応してくれそうなので待ち。
https://github.com/prometheus-community/postgres_exporter/issues/495


## DockerのexporterからホストOSのpostgresにアクセスする場合
ホストOSのpostgresがpg_hba.confでアクセス制限を厳密にやっている時の対策方法。

### docker-compose.yaml
以下の様にnetworksの設定でコンテナのIPアドレスを固定する。


```yaml:docker-compose.yaml
version: '3'

services:
  postgres-exporter:
    container_name: postgres-exporter
    image: quay.io/prometheuscommunity/postgres-exporter
    hostname: postgres-exporter
    extra_hosts:
      # コンテナ内からホストOSのlocalhostへアクセスするための設定
      # ホストOSのdocker0のIPアドレスが渡される
      - "host.docker.internal:host-gateway"
    env_file:
      - ./postgres-exporter/.env
    ports:
      - 127.0.0.1:9187:9187
    restart: always
    networks:
      exporter-network:
        # このコンテナのIPアドレスを以下に固定
        ipv4_address: 192.168.200.1

networks:
  exporter-network:
    ipam:
      driver: default
      config:
        # 16個確保
        - subnet: 192.168.200.0/28
```


### pg_hba.conf
pg_hba.confで、先程固定したexporterのコンテナIPアドレスからの接続許可設定をする。

```
host dbname postgres_exporter 192.168.200.1/32 md5
```


### postgresql.conf
postgresql.confのlistenを書き換える

```diff
-listen_addresses = '127.0.0.1'
+listen_addresses = '*'
```

### 接続確認
exporterからではなくホストOS上からコマンドで接続確認する方法。これで接続確認できれば、おそらくDockerコンテナからも接続できる。

#### docker0のIPアドレスを確認
以下のコマンドでdocker0のIPアドレスを確認。
ここでは、192.168.249.1だったとする

```console
$ /sbin/ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 192.168.249.1  netmask 255.255.255.0  broadcast 192.168.249.255
```

#### psqlで確認
docker0のIPアドレスをホストに指定して接続。

```console
$ psql dbname -p 5432 -U postgres_exporter -h 192.168.249.1 -W
```

## 関連記事
- [DockerコンテナからホストOSのPostgresにアクセス全開設定にせずにログインする方法](https://qiita.com/Esfahan/items/47ade6398aa2f8a466b1)
- [Prometheus MySQL Exporter with Docker](https://qiita.com/Esfahan/items/ccd0b09a681da0b7cb85)
- [Prometheusを使った監視システムをDockerで作る](https://qiita.com/Esfahan/items/0feaedfd771f49ac7ee4)