祝!初めてOSSにPR上げたらマージされたぞヤッター!

頑張った。

とっても軽微な修正だったけど、PR上げる時は本当に緊張した。

何にPR出したの?

出したのは二つ。

一つはAIアプリ開発プラットフォームのDify

github.com

もう一つはmrubyのライブラリ、httpクライアントのmruby-simplehttp

github.com

続きを読む

PrometheusのService Discoveryで、スケールアウト用EC2インスタンスを監視対象に動的追加/削除をする

表題の通りで、Prometheusでそういうことをしようという話。
PrometheusにはService Discoveryの機能があるので「やるぞー」となり「やったぞー」という内容の備忘録。

なお、2022年頃に実施したものなので特に最近のものではない。今ならもしかしたらもう少し良い方法があったりするのだろうか…。

続きを読む

ansibleで空配列の定義と値の入れ込みを1つのタスク内で処理する

そうしたい時もあると思います。

過去、ループで回した値を定義した配列に順次入れ込んでいきたい場合はこんな感じにしていた。

- name: Define empty array
  ansible.builtin.set_fact:
    array: []

- name: Set array
  ansible.builtin.set_fact:
    array: "{{ array + [item] }}"
  loop:
  - "a"
  - "b"

最初のタスク Define empty array で空配列 array を定義して、次のタスクで実際の値を入れていく形。
これで Set array 後、 array の中身は ["a", "b"] となる。
まぁこの書き方でも別に良いのだけど、なんだか冗長だなーと。
できれば空配列の定義と、それに値を入れ込む処理は1タスクでいい感じに処理したい。
で、調べたり検証したところ、以下のように書けば一つの処理で完結できることがわかった。

- name: Define array and Set
  ansible.builtin.set_fact:
    array: "{{ array | default([]) + [item] }}"
  loop:
  - "a"
  - "b"

default フィルターを使うことで、 array が定義されていない場合に空配列 [] が設定される。
この default フィルターは、変数が定義されていない場合にのみ適用されるため、初回以外は無視されるのだ。
その後にループで回した値が入れられていくので、結果は上のタスクと同じとなる。

これをどう使うかというと register で取得したタスクの結果とか、自分で定義した vars の配列をループさせて必要な情報だけ取り出したい時とか、そういう時に使ったりする。

変数定義に1タスク使うのもなんだかなではあるのでこういう形で短縮できると見通しも良くなっていいんじゃないだろうか。
まあ場合によるとは思うけど…。
例えばこのタスクにwhen句があって、場合によって処理されない時があるのに、後続処理では定義したarrayを絶対使う…とかだと、空配列の定義と値の入れ込み処理は分けた方が良さそう。
まあやっぱり処理の方法による…か…。

この書き方は元々確かStack OverFlowに記載されていたのだけど、そのURLは失念…。
どう調べたら出てくるかなあ…。

PrometheusでSSL証明書の有効期限をYYMMDDの年月日形式に変換するPromQL

監視の一環として、PrometheusでSSL証明書の有効期限を追うようにしている。
blackbox_exporterでSSL証明書の有効期限が出せるので、元々死活監視だけをするつもりだったがついでにSSL証明書の有効期限も監視することにした。
blackbox_exporter内の設定はこんな感じ(一部省略)。

module
  cert_expires:
    prober: http
    http:
      method: GET

Prometheus内の設定はこんな感じ(一部省略)。

rule_files:
  - "/etc/prometheus/conf.d/notifiers/slack_notifications.yml"

scrape_configs:
  - job_name: 'cert_expires'
    scrape_timeout: 10s
    scrape_interval: 5m
    metrics_path: /probe
    params:
      module: ['cert_expires']
    file_sd_configs:
    - files:
      - conf.d/cert_expires/*.yml
    relabel_configs:
    - source_labels: [__address__]
      target_label: __param_target
    - source_labels: [__param_target]
      target_label: url
    - target_label: __address__
      replacement: 127.0.0.1:9115

file_sd_configsconf.d/cert_expires/ 以下のymlファイルを全て読むようにしている。
読み込んでいるymlファイルの中身はこんな感じ(一部省略)。

- targets:
  - https://example.com

rule_files で読み込んでいる設定ファイルの中身はこんな感じ(一部省略)。

groups:
- name: blackbox_exporter
  rules:
  - alert: SSLCertificateExpiresIn30days
    expr: round(sort((probe_ssl_earliest_cert_expiry{job="cert_expires"} - time()) / 86400), 1)
    for: 0m
    labels:
      severity: info
    annotations:
      summary: "SSL Certificate expires in 30 days"
      description: "{{ $labels.url }} expires: {{ $value }}"

通知に使っているalertmanagerの設定はこんな感じ(一部省略)。

mute_time_intervals:
- name: daily_mute_1000-0900
  time_intervals:
  - times:
    - start_time: 01:00 # JST 10:00
      end_time:   24:00 # JST 09:00

route:
  - receiver: 'cert_expires'
    continue: false
    group_by:
    - alertname
    matchers:
    - severity="info"
    - job="cert_expires"
    mute_time_intervals:
    - daily_mute_1000-0900

上では30日の設定しか記載していないが、60日前、30日前、14日前、10日前、7日前、3日以内とあり、残り有効期限がどれかに引っかかったら一日に一回、日本時間で9時〜10時の間にSlackに通知を出してくれる。
のだが、これだと有効期限が相対日数でしか出せない。
「あとn日で期限切れになるよ」というのは出せるが、「n年n月n日に期限切れになるよ」というのが出せない。

blackbox_exporterで取得できる有効期限はUnixtimeなので、 有効期限 - 現在時刻 * 86400 をしてやれば「あとn日」は出せる。
最初はこれで出していたのだが、相対日数は実際いつ期限切れになるのかわかりづらい…わかりづらくない…?
「n日後」の実際の月日がパッと見でどうしてもわかりづらいのだ。
ちょっと前に来た通知で14日後とか言われても、じゃあ実際いつ期限切れるんだっけ…?みたいな感じである。
そんなこんなで調べてみたところ、Unixtimeを直接年や月、日、時間に変える関数が存在するらしいことに気付いた。

https://prometheus.io/docs/prometheus/latest/querying/functions/

よし、これを使おう。と思い立ったのが発端で、タイトルのPromQLを作り始めた。

続きを読む

Go言語でメソッドを実装する

Go言語は type で定義した構造体、もしくは別名を付けた型定義に対してメソッドを作成できる。
書式は以下。[]内のものが実際に指定する値。

func ( レシーバ[変数 型] ) メソッド名 ( 引数[変数 型] ) 戻り値[型] { 処理 }

例えば、 Student 構造体に Hello メソッドを作ってみるとする。

type Student struct {
  Name string
  Age int
}

func main() {
  taro := Student{"Tanaka Taro", 11}
  taro.Hello()
}

func (s Student) Hello() {
  fmt.Printf("Hello, %s.\n", s.Name)
}

実行すると以下のようになる。

Hello, Tanaka Taro.

関数と同様参照渡しも使える。
多分メソッドを作成する場合参照渡しが基本になるんじゃないだろうか。
なんとなくだけど、メソッドを使った上で別の変数にそれを代入するのはちょっと煩わしい気がする。
以下では PutAge() メソッドで年齢を更新し、 PrintAge() メソッドで出力してみている。

type Student struct {
  Name string
  Age int
}

func main() {
  taro := Student{"Tanaka Taro", 11}
  taro.PrintAge()

  taro.PutAge(14)
  taro.PrintAge()
}

func (s *Student) PutAge(age int) {
  s.Age = age
}

func (s Student) PrintAge() {
  fmt.Printf("%s age is %d\n", s.Name, s.Age)
}

実行すると以下のようになる。

Tanaka Taro age is 11
Tanaka Taro age is 14

値が書き換わっているが、 PutAge() メソッドで参照渡しをしているため、直接 taro.Age の値を操作できるというわけだ。すごい。
書いてて思ったけどRubyでいうところの破壊的変更っぽいか?どうだろう。
大きい値は値渡しをしてしまうと関数呼び出しの際にコピーしてしまうため、大きい値や構造体の場合は参照渡しをした方が良いとのこと。
うーん...なんだか思考停止で参照渡しにしてしまいそうだな...。
このケースだと値渡しで、このケースなら参照渡し、みたいなのがあると嬉しいなあ。

Go言語構造体と値渡し、参照渡しおさらい

ちょっとGo言語ハンズオンをやっているのだけど、メソッドが出てきてから脳の処理が追いつかなくなってきました。
理解がね...追いついていないのです…。

なのでとりあえず書き出そうかと。

構造体

通常使うとしたら以下

type 構造体の名称 struct {
...
}

// 例
type Student struct {
  Name string
  Age int
}

あまり使わなさそうだが変数に直接定義する場合

var 変数 struct {
...
}

// 例
var taro struct {
  Name string
  Age int
}

構造体を利用する場合は以下

// 前者
taro := Student{"Tanaka Taro", 11}

// もしくは
taro := Student{
  Name: "Tanaka Taro",
  Age: 11,
}

// 後者
taro.Name = "Tanaka Taro"
taro.Age = "11"

これはわかる。まだ。

構造体の値渡しと参照渡し

構造体を引数とした関数を作る場合、値渡しと参照渡しの2つの方法がある。
値渡しの例は以下

func main() {
  taro := Student{"Tanaka Taro", 11}
  // 変数 taro を引数として関数 plus1 を呼び出し、結果を変数 taro_1 に格納する
  taro_1 := plus1(taro)
  // 変数は taro と taro_1 の2つを保持することになる
  fmt.Println(taro)
  fmt.Println(taro_1)
}

func plus1(s Student) Student {
  // s.Age に +1 してから再度書き込む
  s.Age += 1
  // 戻り値は Student型(構造体) なので、 s.Age に +1 した変数 s を return する
  return s
}

参照渡しの例は以下

func main() {
  taro := Student{"Tanaka Taro", 11}
  // 関数 plus1 に変数 taro のポインタを渡す
  plus1(&taro)
  fmt.Println(taro)
}

// ポインタを渡されているため、型の前に * を付けて値を参照できるようにする
func plus1(s *Student) {
  // ポインタ s の Age を操作するため、 (*s) として参照先とし、参照先の Age に +1 する
  // ただし、 s.Age += 1 としてもいける(ポインタレシーバというやつか?)。
  (*s).Age += 1
}

値渡し参照渡しが引っかかりポイントとしてある。
参照渡しの時 (*s) としなくても正しくコンパイルが通るのは以下の暗黙的変換が効いているからだろうか…?

skatsuta.github.io

書籍は読み進めても書いていなかった...。どういうことなんだ...。

とりあえず一旦そういうものとして理解するしかないか...。
この辺なんて検索すれば出てくるのだろうか。