A Day In The Life

とあるプログラマの備忘録

フロー情報はブログに、ストック情報はZenn.devに投稿することにしました。

今後技術記事はZenn.devに投稿します。

長年ブログに技術記事をかいてきましたが、フロー情報とストック情報を分けて投稿することにしました。 今後、技術記事はZenn.devに投稿します。

最近投稿したZenn.devの記事

最近は主にGoの記事を書いています。

引き続き、本ブログをよろしくお願いします。

引き続き、お気持ち表明とか、近況報告、おすすめの本情報なんかはこちらのブログに書いていきます。

GCPマネージドサービス向けLoggerライブラリを開発しました。

GCPマネージドサービス(GAE/GKE/Cloud Run)向けのGoのLoggerライブラリを開発しました。 github.com

Google App Engine 第1世代のロガー(google.golang.org/appengine/log)からの移行ができるように関数のインフタフェースを揃えてあります。

CABTMIDILocalPeripheralViewControllerを使わずにMIDI over BLEでアドバータイズする方法

ワイヤレスMIDI接続をしてアプリから他の端末にMIDIデータを送信したい

CoreAudioKit の CABTMIDILocalPeripheralViewController を使うとiPhone(またはiPad)アプリ上に MIDI over Bluetooth LE のアドバータイズの設定画面を表示することができます。

CABTMIDILocalPeripheralViewControllerを使うと設定画面が立ち上がりアドバータイズ設定ができるようになります

上記画面の Advertise MIDI Service の項目をオンにするとアプリ側を BLE ペリフェラルとしてアドバータイズすることができます。

CABTMIDILocalPeripheralViewControllerを使わずにアドバータイズしたい

SwiftUI や SpriteKit で画面構築をしていると CABTMIDILocalPeripheralViewController のような UIViewController を継承したクラスを使うのは少し大変です。そこで CABTMIDILocalPeripheralViewController を使わずに直接 CoreBluetooth を使ってMIDI over BLE のアドバータイズができないか調べてみました。その結果サービスのIDとキャラクタリスティックのIDに特定の値を指定するとできることがわかりました。 以下は SpriteKit のシーンで BLE のアドバータイジングをするコードです。

import SpriteKit
import CoreBluetooth

class GameScene: SKScene {
    var manager: CBPeripheralManager!
    var service: CBMutableService!
    // MIDI Service UUID
    let serviceID = CBUUID(string: "03B80E5A-EDE8-4B33-A751-6CE34EC4C700")
    // MIDI I/O Characteristic UUID
    let characteristicID = CBUUID(string: "7772E5DB-3868-4112-A1A9-F2669D106BF3")

    override func didMove(to view: SKView) {
        super.didMove(to: view)
        self.manager = CBPeripheralManager(delegate : self, queue : nil, options: nil)
    }
}

extension GameScene: CBPeripheralManagerDelegate {
    // ペリフェラルの状態通知
    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        guard peripheral.state == .poweredOn else {
            print("error: \(peripheral.state)")
            return
        }
        
        print("bluetooth pwoer on")
        // サービスとキャラクタリスティックの追加
        self.service = CBMutableService(type: self.serviceID,
                                       primary: true)

        let properties: CBCharacteristicProperties = [.notify, .read, .writeWithoutResponse]
        let permissions: CBAttributePermissions = [.readable, .writeable]
        let characteristic = CBMutableCharacteristic(type: self.characteristicID,
                                                     properties: properties,
                                                     value: nil, permissions: permissions)
        
        self.service.characteristics = [characteristic]
        self.manager.add(self.service)
    }
    // サービス追加の成功失敗通知
    func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
        guard (error == nil) else {
            print("Add service failed")
            return
        }
        print("Add service succeeded")
        
        // アドバタイズ開始
        let advertisementData = [CBAdvertisementDataLocalNameKey: "Penguin Drums",
                                 CBAdvertisementDataServiceUUIDsKey: [self.serviceID]] as [String : Any]
        manager.startAdvertising(advertisementData)
        
        print("Service starts advertising!")
    }
}

サービスのIDには 03B80E5A-EDE8-4B33-A751-6CE34EC4C700 をキャラクタリスティックのIDには 7772E5DB-3868-4112-A1A9-F2669D106BF3 を指定します。またキャラクタリスティックのプロパティに Read, Write, Notify を指定します。

参考記事

SpriteKit SceneをiPhone/iPadのホームバーに対応させる

3年間放置していたiOSアプリをホームバーに対応させた話

かれこれ3年くらい放置していた iPhone/iPad 両対応のアプリをホームバーに対応させようと思ったら結構大変だったのでその記録です。 ちなみに放置していたアプリはこちらです。

ピアノ - 即興ピアノ

ピアノ - 即興ピアノ

  • taisuke fujita
  • ミュージック
  • 無料
apps.apple.com

対応前の設定はこんな感じ

4.7インチの iPhone(6, 6S, 7, 8, 第2世代SEなど1334 * 750ピクセル)をベースにiPadにも対応させるため、SKScene の幅を1334ピクセル高さを1000.5ピクセル(4:3)に設定し、アンカーポイントを(0, 0)に設定してました。 以前の画面 4:3の iPad を表示領域として、スプライトを16:9の iPhone の領域におさまるように配置するイメージです。この設定だと iPhone だとぴったりで iPad だと上下に余白ができます。 シーンのスケールモードの設定は aspectFill に設定してました。

class GameViewController: UIViewController {
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        
        if let view = self.view as! SKView? {
            // Load the SKScene from 'GameScene.sks'
            if let scene = GameScene(fileNamed: "GameScene") { 
                // Set the scale mode to scale to fit the window
                scene.scaleMode = .aspectFill
                // Present the scene
                view.presentScene(scene)
            }
        }
    }
}

ホームバーの無い iPhone 8iPad Pro 第2世代では問題なく表示できていました。しかし iPhone X 以降(またはiPad Pro 11, 12.9インチ)のホームバー付きの端末で表示させる以下のようになりました。 はみ出た 全体が拡大されてスプライトがはみ出ています。

ホームバーに対応する手順

iPhone X 以降(またはiPad Pro 11, 12.9インチ)のホームバーに対応する手順は以下のようになります。

  1. SKSceneのサイズを16:9に合わせて変更する
  2. アンカーポイントを(0, 0)から(0.5, 0.5)に変更する
  3. presentSceneを呼び出すタイミングでSceneのサイズを調整する

手順1と2でシーンエディタの修正をします。手順3でプログラムの修正をします。

SKSceneのサイズを16:9に合わせて変更する

SKScene のサイズを16:9に合うように変更します。私の場合は1334 * 1000.5だった設定を1334 * 750に変更しました。16:9であれば960 * 540だったり、2208 * 1242だったりでも問題ないようです。作成したスプライトの画像サイズに合うように適宜変更すれば良いようです。ポイントは縦横比が16:9になるように SKScene のサイズを設定することです。

Sceneのアンカーポイントを中央にする

16:9 の iPhone の画面にぴったり合うようにスプライトを配置すると iPhone 11 系では左右に余白ができ、iPad 系では上下に余白ができます。この余白に影響を受けないようにするにはアンカーポイントを中央(0.5, 0.5)に設定する必要があります。シーンのアンカーポイントを変更するとスプライトの位置が変わってしまうので適宜スプライトの位置も変更します(地味にこの作業が一番めんどくさい)。

ここまでの修正を反映するとこんな感じです

修正後の画面

presentSceneを呼び出すタイミングでSceneのサイズを調整する

UIView の presentScene メソッドを呼ぶタイミングで、UIView のサイズ(機種によってサイズがかわる)と SKScene のサイズの比率を計算して SKScene のサイズを調整してあげます。スケールモードの設定は aspectFill から aspectFit に変更します。

class GameViewController: UIViewController {
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        
        if let view = self.view as! SKView? {
            // Load the SKScene from 'GameScene.sks'
            if let scene = GameScene(fileNamed: "GameScene") {
                var factor = view.frame.size.height / scene.size.height
                // iPad
                if view.frame.size.width / factor < scene.size.width {
                    factor = view.frame.size.width / scene.size.width
                }
                scene.size = CGSize(width: view.frame.size.width / factor,
                                       height: view.frame.size.height / factor)
                
                // Set the scale mode to scale to fit the window
                scene.scaleMode = .aspectFit
                // Present the scene
                view.presentScene(scene)
            }
        }
    }
}

手順を適用した結果はこちら

手順1から3を適用して修正した結果は以下のようになります。

iPhone 8

ホームバーなし(ホームボタンあり)の従来型の iPhone 画面です。 iPhone8

iPhone 11

ホームバーありの新型の iPhone 画面です。左右に余白ができています。 iPhone11

iPad Pro 11インチ

ホームバーありの新型の iPad 画面です。上下に余白ができています。 iPad Pro

参考記事

Time型をJSONに変換するとtime.Locationの情報が欠落する場合の対処法

Time 型のフィールドを含む構造体を JSONエンコードJSON からデコードすると特定条件下で Time 型オブジェクトの time.Location の情報が失われることがあります。 以下のプログラムを見てください。

package main

import (
    "fmt"
    "time"
    "encoding/json"
)

type Hoge struct {
    Name string
    CreatedAt time.Time
}

func main() {
    time.Local = time.UTC
 
    // Hoge構造体を生成する
    h1 := Hoge{
        Name: "aaa", 
        CreatedAt: time.Unix(1593077427, 0),
    }

    // Jsonsに変換
    j, _ := json.Marshal(h1)
 
    // Jsonから復元する
    h2 := Hoge{}
    json.Unmarshal(j, &h2)
 
    fmt.Printf("h1の出力結果\n%#v\n", h1)
    fmt.Printf("h2の出力結果\n%#v\n", h2)
}

出力結果を確認すると、JSON 変換前の Hoge オブジェクト(変数名 h1)の CreatedAt フィールドは loc に (*time.Location)(0x5c7a40) がセットされているのに対し、JSON 変換後に復元した Hoge オブジェクト(変数名 h2)の CreatedAt フィールドは loc が nil になっています。

h1の出力結果
main.Hoge{Name:"aaa", CreatedAt:time.Time{wall:0x0, ext:63728674227, loc:(*time.Location)(0x5c7a40)}}
h2の出力結果
main.Hoge{Name:"aaa", CreatedAt:time.Time{wall:0x0, ext:63728674227, loc:(*time.Location)(nil)}}

これ %+v で出力しても違いがわからないので注意が必要です。%#v で出力してみないとわからないです。

time.Localにtime.UTCをセットした場合のみ起きる現象

JSON に変換すると Time 型フィールドの loc 情報が nil になってしまう現象は time.Localにtime.UTCをセットした場合のみ発生します。

func main() {
    time.Local = time.UTC
    ...
    ... その他、処理
    ...
}

以下のように UTC 以外のローカルタイムを設定している場合は発生しません。

func main() {
    loc, _ := time.LoadLocation("Asia/Tokyo")
    time.Local = loc
    ...
    ... その他、処理
    ...
}

ローカル時間をUTCにセットするとreflect.DeepEqualが常にfalse判定されてしまう

time.Location の情報が失われると、Time 型フィールドを含む構造体を reflect.DeepEqual で比較した時に意図通り判定されなくなります。以下のプログラムを見てください。

package main

import (
    "fmt"
    "time"
    "encoding/json"
    "reflect"
)

// Hoge構造体の定義は省略

func main() {
    time.Local = time.UTC
 
    // Hoge構造体を生成する
    h1 := Hoge{
        Name: "aaa", 
        CreatedAt: time.Unix(1593077427, 0),
    }

    // Jsonsに変換
    j, _ := json.Marshal(h1)
 
    // Jsonから復元する
    h2 := Hoge{}
    json.Unmarshal(j, &h2)
 
    result := reflect.DeepEqual(h1, h2)
    fmt.Printf("reflect.DeepEqual: %v", result)
}

result の出力結果は true ではなく false になります。

reflect.DeepEqual: false

対処方法

これに対処する方法はいくつか存在します。

  • Time.Equal メソッドを使って判定する
  • UTC に変換する
  • go-cmp を使って判定する
  • time.Local に nil をセットする

個別に対処方法を見ていきましょう

Time.Equal メソッドを使って判定する

こちらは time パッケージの Godoc に記載されている方法です。

result := h1.CreatedAt.Equal(h2.CreatedAt)
fmt.Printf("Time.Eqaul: %v", result)

出力結果は true になります。

Time.Eqaul: true

ただこの方法だと Time 型のフィールドを含む構造体同士を比較する場合はフィールドを一つずつ比較しないといけなくなります。

UTC に変換する

Time オブジェクトを生成するタイミングで UTC メソッドを使って UTC 変換に変換します。

func main() {
    time.Local = time.UTC
 
    // Hoge構造体を生成する
    h1 := Hoge{
        Name: "aaa", 
        // UTCに変換する
        CreatedAt: time.Unix(1593077427, 0).UTC(),
    }

    // Jsonsに変換
    j, _ := json.Marshal(h1)
 
    // Jsonから復元する
    h2 := Hoge{}
    json.Unmarshal(j, &h2)
 
    result := reflect.DeepEqual(h1, h2)
    fmt.Printf("reflect.DeepEqual: %v", result)
}

Hoge 構造体を生成するときに UTC メソッドを使って変換する必要がありますが結果はちゃんと true になります。

reflect.DeepEqual: true

Equal メソッドを使うよりも良さそうではありますが UTC メソッド呼び出しを忘れるとバグの温床になります。個人的にはあまりおすすめしません。

go-cmp を使って判定する

reflect.DeepEqual を使う代わりに、ユニットテストで構造体を比較する時の定番パッケージ go-cmp を使って比較します。

import (
    "fmt"
    "time"
    "encoding/json"
    "github.com/google/go-cmp/cmp"
    "reflect"
)

// Hoge構造体の定義は省略

func main() {
    time.Local = time.UTC
 
    // Hoge構造体を生成する
    h1 := Hoge{
        Name: "aaa", 
        CreatedAt: time.Unix(1593077427, 0),
    }

    // Jsonsに変換
    j, _ := json.Marshal(h1)
 
    // Jsonから復元する
    h2 := Hoge{}
    json.Unmarshal(j, &h2)
 
    result1 := reflect.DeepEqual(h1, h2)
    fmt.Printf("reflect.DeepEqual: %v\n", result1)
 
    result2 := cmp.Equal(h1, h2)
    fmt.Printf("cmp.Equal: %v", result2)
}

出力結果は以下のようになります。

reflect.DeepEqual: false
cmp.Equal: true

go-cmp は Time 型フィールドを比較する時、内部で Time.Equal メソッドを使って判定してくれるので reflect.DeepEqual が false 判定してしまう場面でも意図通り判定してくれます。

time.Local に nil をセットする

go-cmp も良いのですが Go の標準パッケージだけでなんとかするなら、main 関数で time.Local に nil をセットする方法があります。

package main

import (
    "fmt"
    "time"
    "encoding/json"
    "reflect"
)

// Hoge構造体の定義は省略

func main() {
    // nilをセットする
    time.Local = nil
 
    // Hoge構造体を生成する
    h1 := Hoge{
        Name: "aaa", 
        CreatedAt: time.Unix(1593077427, 0),
    }

    // Jsonsに変換
    j, _ := json.Marshal(h1)
 
    // Jsonから復元する
    h2 := Hoge{}
    json.Unmarshal(j, &h2)
 
    result := reflect.DeepEqual(h1, h2)
    fmt.Printf("reflect.DeepEqual: %v\n", result)
}

出力結果は以下のようになります。意図通り reflect.DeepEqual の結果が true になります。

reflect.DeepEqual: true

参考記事

Prometheusで収集したメトリクスを使ってCloud Monitoringのダッシュボードを作成する

Prometheus で収集した GKE アプリケーションのメトリクスを GCP の Cloud Monitoring(旧Stackdriver Monitoring) にエクスポートして Cloud Monitoring のダッシュボードを作成する方法を紹介します。Prometheus といえば Grafana を使ってダッシュボードを作成することが多いですが GCP 環境では Cloud Monitoring に一元化して監視できたほうが何かと便利です。あえて Grafana を使わず Cloud Monitoring でメトリクスを可視化します。

以下の記事で GKE 上に構築した Redis のメトリクスを Prometheus を使って収集しました。この環境を利用して Prometheus から Cloud Monitoring にメトリクスをエクスポートする方法を紹介します。

glassonion.hatenablog.com

メトリクスを Cloud Monitoring にエクスポートする方法

Prometheus のメトリクスを Cloud Monitoring にエクスポートする方法は以下の2つがあります。

  • prometheus-to-sd
  • stackdriver-prometheus-sidecar

prometheus-to-sd は prometheus text format (Prometheusのメトリクスデータの形式)を Cloud Monitoring 形式に変換してエクスポートすることができるツールです。ただし prometheus-to-sd は GoogleKubernetes チームが必要とする指標のみをサポートするように開発されたもので使用用途が限定されているのとドキュメントが少ないのであまりおすすめしません(使ったことがないので実際のところどうなのかわかりませんが)。

一方の stackdriver-prometheus-sidecar は Stackdriver が開発した Prometheus のメトリクスを Cloud Monitoring にエクスポートするためのツールです。GCP の公式ドキュメントでも stackdriver-prometheus-sidecar を使って Cloud Monitoring に指標をエクスポートする方法が紹介されています。

この記事ではドキュメントも多く情報が充実している stackdriver-prometheus-sidecar を使います。

メトリクスを Cloud Monitoring にエクスポートするためのポイント

ポイントは3つあります。

  • Prometheus のメトリクス収集は static_config ではなくサービスディスカバリを使って行う
  • Prometheus のメトリクスデータを共有ボリュームに書き込むように設定する
  • stackdriver-prometheus-sidecar のコンテナを Prometheus のサイドカーとして配置する

Prometheus のメトリクス収集は static_config ではなくサービスディスカバリを使って行う

static_config だと stackdriver-prometheus-sidecar が Prometheus から情報を読み出せないので Prometheus のメトリクス収集は static_config ではなく サービスディスカバリで行ってください。

Prometheus のメトリクスデータを共有ボリュームに書き込むように設定する

stackdriver-prometheus-sidecar は共有ボリュームにある WAL(write-ahead-log) ファイルにアクセスしてメトリクスをエクスポートするので Prometheus が共有ボリュームに対してメトリクスデータを書き出すように設定する必要があります。WAL については以下のページに詳細があるので気になる方は参照してみてください。

stackdriver-prometheus-sidecar のコンテナを Prometheus のサイドカーとして配置する

stackdriver-prometheus-sidecar は Prometheus の収集したメトリクスを読み出して Cloud Monitoring にエクスポートするので、stackdriver-prometheus-sidecar を Prometheus のサイドカーとして配置します。

構成

今回の Kubernetes の構成は以下のようになります。

sequence dialog

Prometheus の Deployment の設定

それでは実際に Prometheus のメトリクスを Cloud Monitoring にエクスポートする環境を構築してみます。Prometheus と stackdriver-prometheus-sidecar のコンテナを配置する Deployment ファイルを以下のように作成してください。

Deployment ファイルの重要な設定をみていきましょう。

共有ボリュームにデータを書き出すための設定

以下はPrometheus のデータ書き込みを共有ボリュームにするための設定です。23行目部分の storage.tsdb.path=/data で指定しています。

args:
  - '--config.file=/etc/prometheus/config/prometheus.yaml'
  - '--storage.tsdb.path=/data' #共有ボリュームに書き込むための設定

stackdriver-prometheus-sidecarコンテナの配置

以下の31行目から44行目が stackdriver-prometheus-sidecar コンテナの配置設定になります。 {{GCPプロジェクトID}}{{クラスタ名}} はお使いの GCP プロジェクトの環境に合わせて変更してください。

- name: sidecar
  image: gcr.io/stackdriver-prometheus/stackdriver-prometheus-sidecar:0.7.3
  args: [
    "--stackdriver.project-id", "{{GCPプロジェクトID}}",
    "--stackdriver.kubernetes.cluster-name", "{{クラスタ名}}",
    "--stackdriver.kubernetes.location", "asia-northeast1",
    "--prometheus.wal-directory", "/data/wal",
    "--prometheus.api-address", "http://127.0.0.1:9090",
  ]
  ports:
  - containerPort: 9091
  volumeMounts: # 共有ボリュームのマウント
  - name: data-volume
     mountPath: /data

こちらの記事で作成した prom-deployment.yaml ファイルを今回作成した yaml ファイルに置き換えて Kubernetes にデプロイすれば構築は完了です。

Metrics explorerでグラフを作成する

ここまでの設定で Prometheus のメトリクスを Cloud Monitoring の外部指標として取得できるようになりました。Cloud Monitoring で外部指標をグラフ化する手順は以下のようになります。

  1. GCP の Cloud Console で、[Monitoring] を選択します
  2. [Monitoring] のナビゲーションパネルで、[Metrics explorer] をクリックします
  3. [Find resource type and metric] メニューで、次(4と5)の手順を行います
  4. [Resource type] として [Kubernetes Container](k8s_container)を選択します(gke_containerではないので注意)
  5. [Metric] フィールドには、接頭辞 external.googleapis.com/prometheus/ を含む指標を選択します

以下は外部指標 external.googleapis.com/prometheus/redis_cpu_user_seconds_total をグラフ化したイメージです。

f:id:glass-_-onion:20200529192144p:plain

参考

Prometheus のサービスディスカバリを使って Kubernetes 上に構築した Redis を監視する

こちらの記事で Prometheus の static_config を設定して Redis のメトリクスを取得する方法について紹介しました。

glassonion.hatenablog.com

Prometheus で監視対象のサーバの設定をする方法は static_config の他に Service Discovery があります。static_config はわかりやすく簡単に設定できるので入門にはとても良いのですが、実運用を考えると static_config よりも Service Discovery を使ってメトリクスを取得するのがおすすめです。この記事では Service Discovery を使って監視対象を設定する方法を紹介します。

前回のおさらい

前回の記事では Kubernetes 上に構築したマスター/スレーブ構成の Redis を Prometheus の static_config を使って監視するまでの流れを紹介しました。

前回作成したファイルは以下です。

  • 監視対象のファイル
    • redis-service.yaml(Redisのサービス)
    • redis-ss.yaml(RedisのStatefulSet)
    • redis.conf(Redisの設定ファイル)
    • launch.sh(Redisの起動ファイル)
  • Prometheus関連のファイル
    • prom-service.yaml(Prometheusのサービス)
    • prom-deployment.yaml(Prometheusのデプロイメント)
    • prometheus.yaml(Prometheusのコンフィグファイル)

今回は主に prometheus.yaml に関する内容になります。それ以外は権限回りの設定で RBAC ファイル追加や prom-deployment.yaml の修正をします。

サービスディスカバリを使う場合のPrometheusコンフィグの設定

サービスディスカバリの説明やコンフィグの内容の説明は後回しにして Prometheus のコンフィグファイルの設定をしてみます。

前回は static_configs を使って以下のように設定しました。

scrape_configs:
- job_name: redis_exporter
  static_configs:
  - targets:
    - redis-0.redis.default.svc:9121
    - redis-1.redis.default.svc:9121
    - redis-2.redis.default.svc:9121

static_configs の場合は監視する Pod を一つづつ targets に設定する必要があります。直感的でわかりやすいものの Pod を増やしたり減らしたりした場合に毎回修正が必要になるのでコンフィグファイルのメンテナンスが大変です。サービスディスカバリを使う場合は Pod をひとつひとつ指定するのではなく、「サービスを介してポート 9121 を公開している Pod を検知すること」というような指定をします。具体的に以下のようになります。

static_configs に比べると設定行数も増えて複雑になりましたが、こうすることで監視対象の Pod が増減しても設定ファイルの修正なしで済みます。

PrometheusのService Discovery

Prometheus の Service Discovery はコンフィグファイルに設定された情報から動的に監視対象を検知してくれる仕組みです。Prometheus の Service Discovery には以下の種類があります。

  • azure_sd_config
  • consul_sd_config
  • dns_sd_config
  • ec2_sd_config
  • openstack_sd_config
  • file_sd_config
  • gce_sd_config
  • kubernetes_sd_config
  • marathon_sd_config
  • nerve_sd_config
  • serverset_sd_config
  • triton_sd_config

Configuration | Prometheus から引用

Kubernetes のノードや Kubernetes 上に構築したサービスまたは Pod を監視する場合は kubernetes_sd_config を使います。kubernetes_sd_config は Kubernetes 向けの Service Discovery です。Prometheus のコンフィグに kubernetes_sd_config を設定することによって Prometheus が kube-apiserver(Kubernetes' REST API)を使って Pod や Service のメトリクスを収集することができるようになります。

static_config と kubernetes_sd_config の監視対象に対するアクセス方法の違いは以下のようになります。

種類 監視対象に対するアクセス方法
static_config HTTP アドレスを直指定してアクセスする
kubernetes_sd_config kube-apiserver を使って監視対象を探してから HTTP アクセスする

kubernetes_sd_configのロールの種類

kubernetes_sd_config は以下の5種類のロールを指定してメトリクスを取得します。種類によってそれぞれ検知できる監視対象が異なります。

Role 内容
node nodeを検出する
endpoints サービスに紐づいたPodを検出する
service サービスを検出する(Podは検出できない)
pod サービスに紐づいていないPodも含めて検出する
ingress ingressを検出する

Pod の監視設定をする場合は endpoints または pod を使います。Pod はサービス経由で公開していることが多いので endpoints を使うことが多いです。

設定したprometheus.yamlの説明

Prometheus のサービスディスカバリがどのようなものか理解できたと思うので、先ほど設定した prometheus.yaml ファイルの内容について説明していきます。

まずはロールの設定から見ていきます。以下はロールに endpoints を指定してサービス経由の Pod を検出できるようにしています。

kubernetes_sd_configs:
- role: endpoints

次は source_labels の action の部分です。こちらを指定することによりどのような条件の Pod を検知するか設定することができます。以下はネームスペース名 default かつポート番号が9121の Pod を検出するように指定しています。action には keep 以外に drop を指定することができます。drop を指定すると監視対象から外れます(明示的に監視対象から外したいときに使います)。

- source_labels:
  - __meta_kubernetes_namespace
  - __meta_kubernetes_pod_container_port_number
  regex: default;9121
  action: keep

最後に Prometheus の監視画面に表示される job 名と pod 名を設定します。こちらの設定は表示の設定なのでこだわりがなければ無くても問題ありません。

- source_labels:
  - __meta_kubernetes_service_name
  target_label: job
- source_labels:
  - __meta_kubernetes_pod_name
  target_label: pod

以上で Kubernetes 上に構築した Pod の監視設定は終わりです。

Kubernetes 1.8以上はRBACの設定が必要

Prometheus の static_config は直接 HTTP アドレスを指定してメトリクスを取得するので Kubernetes の権限まわりのことを考慮する必要はありませんでした。一方サービスディスカバリを使う場合は kube-apiserver から Kubernetes の情報を取得する必要があります。kube-apiserver から情報を取得するためには RBAC を設定する必要があります。GKE Kubernetes 1.7までは RBAC がまだ stable になっていなかったので設定は不要でした。しかし GKE Kubernetes 1.8から RBAC が stable になり、Prometheus の Pod に対して RBAC を設定しないと、サービスディスカバリを使った監視対象の検出ができなくなりました。RBAC のカスタムロールを作成して Prometheus の Pod に権限を付与する必要があります(監視対象のリソースに対して RBAC を設定することもできますが、それをすると Redis 側に修正を入れないといけなくなるため今回はその方法はとりません)。

RBAC を設定する

RBAC を設定するときにポイントは以下です。

  • Prometheus と監視対象(今回はRedis)の Namespace を分ける
    • Prometheus 側は stats、Redis 側は default ネームスペースを設定しています
  • カスタムロールを作成する
    • all-readerという名前のカスタムロールを作成します
  • Prometheus 側の Namespace にサービスアカウントを作成する
    • prometheusという名前のサービスアカウントを作成します
  • 作成したサービスアカウントにカスタムロールを紐づけて権限を付与する
    • prometheusサービスアカウントとall-readerロールを紐付けます
  • Prometheus の Pod にサービスアカウントを設定して監視対象を検出できるようにする
    • 別途 Prometheus の Deployment の yaml を修正します

上記ポイントを加味した RBAC の設定は以下のようになります。

Prometheus の Pod にサービスアカウントを設定して監視対象を検出できるようにする

RBAC の設定ができたので Prometheus の Pod に対して先ほど作成したサービスアカウントを以下のように紐付けます。

前回の記事の Deployment 設定に1行追加(17行目)しただけです。

... 省略 ...
    spec:
      serviceAccount: prometheus # 追加した設定
      containers:
... 省略 ...

デプロイして Prometheus の管理画面から Redis の状態を確認する

それでは設定したファイルをデプロイして Prometheus が監視対象を検知できるか確認してみます。Kustomize ファイルの内容は前回の記事から rbac.yaml が追加になった以外は同じです。

resources:
- redis-service.yaml
- redis-ss.yaml
- prom-service.yaml
- prom-deployment.yaml # 今回修正したファイル
- brac.yaml # 新規追加したファイル
configMapGenerator:
- name: redis-config
  files:
    - launch.sh
    - redis.conf
- name: prometheus-config
  namespace: stats
  files:
    - prometheus.yaml # 今回修正したファイル

今回修正したのは prom-deployment.yaml と prometheus.yaml の2ファイルです。あと新たに brac.yaml が追加になりました。 kustomize.yaml を base ディレクトリに保存して以下のコマンドを実行します。

kubectl apply -k base

デプロイが終わったら、ブラウザから Prometheus の管理画面(http://35.1x9.1x0.XXX:9090)にアクセスして Targets 画面に移動します。以下のように Redis Exporter が認識されて入れば成功です。

f:id:glass-_-onion:20200509194108p:plain
targets画面

まとめ

Prometheus のサービスディスカバリを使って Kubernetes のメトリクスを取得するには kubernetes_sd_config を使います。Kubernetes 1.8以上で Prometheus のサービスディスカバリを使うには RBAC の設定が必要です。

サンプルコード

今回のサンプルコードはこちらに置いています。

github.com

参考書籍

Prometheus について一番詳しく書かれている書籍です。

参考記事

関連記事