CLOVER🍀

That was when it all began.

Fluent Bitで、複数のOutputを使いたい

これは、なにをしたくて書いたもの?

Fluentdでは、ひとつのレコードを複数の出力先で扱う場合、copy Output Pluginを使用します。

copy - Fluentd

これを、Fluent Bitでやる場合はどうするのかな?ということで、調べてみました。

結論は、とても単純でしたが。

Fluent Bitで複数のOutputを使う

Fluent Bitのドキュメントには、Fluentdでいうcopy Output Pluginに相当するものはありません。

どうするのかな?と思ったら、ストレートに解答したissueがありました。

Output plugin · Issue #721 · fluent/fluent-bit · GitHub

これを見ると、タグが複数のOutput Pluginにマッチするように設定を書けばよいみたいです。とてもシンプル。

というわけで、今回はこれを確認してみます。

環境

今回の環境は、こちらです。

$ uname -srvmpio
Linux 4.15.0-101-generic #102-Ubuntu SMP Mon May 11 10:07:26 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic

Fluent Bitのバージョンは、こちら。

$ /opt/td-agent-bit/bin/td-agent-bit --version
Fluent Bit v1.4.4

複数のOutputを使うように、Fluent Bitを設定する

では、Fluent Bitの設定を行います。結果がこちら。

$ grep -v '#' /etc/td-agent-bit/td-agent-bit.conf 
[SERVICE]
    Flush        5

    Daemon       Off

    Log_Level    info

    Parsers_File parsers.conf
    Plugins_File plugins.conf

    HTTP_Server  Off
    HTTP_Listen  0.0.0.0
    HTTP_Port    2020

[INPUT]
    Name   tcp
    Listen 0.0.0.0
    Port   5170
    Format json
    Tag    mytag.message

[OUTPUT]
    Name  stdout
    Match mytag.*

[OUTPUT]
    Name  file
    Match mytag.*
    Path  /tmp/file-output.txt

Inputにはtcpを使うことにします。

[INPUT]
    Name   tcp
    Listen 0.0.0.0
    Port   5170
    Format json
    Tag    mytag.message

ここでマッチしたタグが、Stdout Output PluginとFile Output Pluginの両方にマッチするように設定します。

[OUTPUT]
    Name  stdout
    Match mytag.*

[OUTPUT]
    Name  file
    Match mytag.*
    Path  /tmp/file-output.txt

設定後、Fluent Bitを再起動してから確認してみましょう。

メッセージを送ってみます。

$ echo '{"message": "Hello Fluent Bit!!"}' | nc localhost 5170

標準出力側で確認。

$ sudo journalctl -u td-agent-bit -f

...

May 24 15:27:41 ubuntu1804.localdomain td-agent-bit[1659]: [0] mytag.message: [1590334056.894144629, {"message"=>"Hello Fluent Bit!!"}]

ファイル側を確認。

$ cat /tmp/file-output.txt
mytag.message: [1590334056.894145, {"message":"Hello Fluent Bit!!"}]

両方のOutputに、同じレコードが渡っていますね。

というわけで、複数のOutputを使いたかったら、その分のOutputにマッチするようにタグを設定すればよい、と。

Terraformで、リソースを作成する・しないをコントロールしたい

これは、なにをしたくて書いたもの?

Terraformでリソースを作るにあたり、条件によって作る・作らないをコントロールしたいなと。

解法としては繰り返しを使うみたいなのですが、自分でちゃんと追ってみましょうか、と。

環境とお題

今回の環境は、こちら。

$ terraform version
Terraform v0.12.25
+ provider.mysql v1.9.0

ProviderはMySQLを使い、データベースの作成をコントロールしてみましょう。MySQL自体は、8.0.20を使います。

雛形

まずは、定型的な雛形を用意。
main.tf

terraform {
  required_version = ">= 0.12.25"
}

provider "mysql" {
  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"

  version = "1.9.0"
}

こちらをベースにカスタマイズしていきましょう。

データベースを複数作る/作らないをコントロールする

Terraformでリソースを複数作成するには、countまたはfor_eachを使用します。

どちらも、指定した数だけリソースを作成することができます。

この時、countを0にしたり、for_eachに空のコレクションを与えるようにすると、そのリソースを作成しないようにすることが
できます。

countで実現する

こんな感じで指定すると、この例だとデータベースが5つ作成されます。

locals {
  create_count = 5
}

resource "mysql_database" "this" {
  count = local.create_count

  name = format("my_database%d", count.index + 1)
}

output "database_names" {
  value = mysql_database.this[*].name
}

Outputとしては、作成したデータベース名を全部返すようにしましょう。

確認。

$ terraform apply -auto-approve
mysql_database.this[3]: Creating...
mysql_database.this[0]: Creating...
mysql_database.this[2]: Creating...
mysql_database.this[4]: Creating...
mysql_database.this[1]: Creating...
mysql_database.this[0]: Creation complete after 0s [id=my_database1]
mysql_database.this[2]: Creation complete after 0s [id=my_database3]
mysql_database.this[3]: Creation complete after 0s [id=my_database4]
mysql_database.this[1]: Creation complete after 0s [id=my_database2]
mysql_database.this[4]: Creation complete after 0s [id=my_database5]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

Outputs:

database_names = [
  "my_database1",
  "my_database2",
  "my_database3",
  "my_database4",
  "my_database5",
]

この時、countに指定する値を0にすると

locals {
  create_count = 0
}

データベースは作成されなくなります。

$ terraform apply -auto-approve

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

database_names = []

Outputは、空のlistになりましたね。

for_eachで実現する

続いて、for_eachで実現してみましょう。for_eachを使う場合は、list(をsetに変換する)かmapを使いますが、今回はlistで。

locals {
  database_names = ["database1", "database2"]
}

resource "mysql_database" "this" {
  for_each = toset(local.database_names)

  name = each.value
}

この例では、データベースが2つできます。

$ terraform apply -auto-approve
mysql_database.this["database1"]: Creating...
mysql_database.this["database2"]: Creating...
mysql_database.this["database1"]: Creation complete after 0s [id=database1]
mysql_database.this["database2"]: Creation complete after 0s [id=database2]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

空のlistを渡した場合は

locals {
  database_names = []
}

データベースは作成されません。for_eachでも、countと同じことができますね。

$ terraform apply -auto-approve

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

ところで、for_eachの方にはOutputの定義がありません。

output "database_names" {
  value = mysql_database.this[*].name
}

これは、先ほどの定義のままだと、こんな感じでエラーになるからです…。

Error: Unsupported attribute

  on main.tf line 33, in output "database_names":
  33:   value = mysql_database.this[*].name

This object does not have an attribute named "name".

Splat Expressionsは、listだけみたいですねぇ…。

Splat Expressions

for_each and splat · Issue #22476 · hashicorp/terraform · GitHub

もしも指定するのなら、固定の要素にするか

output "database_names" {
  value = mysql_database.this["database1"].name
}

listに変換することになります。

output "database_names" {
  value = values(mysql_database.this)[*].name
}

単一のリソースを作る/作らないをコントロールする

ここまでは「複数個のリソースができる」ことを前提に書いてきましたが、「作るとしてもひとつ、もしくは作らない」という
パターンを考えてみたいと思います。

countで実現する

こういうのは、countでやった方がわかりやすいでしょうか?あと、作る・作らないのコントロールは、boolですると良さそうですよね。

locals {
  create = true
}

resource "mysql_database" "this" {
  count = local.create ? 1 : 0
  name = "my_database"
}

こんな感じで、作成するなら1、しないなら0にしておけばOKです。

  count = local.create ? 1 : 0

ただ、Outputにはちょっと困ります。

こんな感じの指定だとlistになるので、呼び出し元がlistとして扱うことになってしまいます。

output "database_names" {
  value = mysql_database.this[*].name
}

単一のリソースを作る前提の場合、これは嬉しくないですね。

かといって、こんな書き方をすると

output "database_name" {
  value = mysql_database.this[0].name
}

リソースを作成する時はいいのですが

$ terraform apply -auto-approve
mysql_database.this[0]: Creating...
mysql_database.this[0]: Creation complete after 0s [id=my_database]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

database_name = my_database

作成しない場合は

locals {
  create = false
}

エラーになってしまいます。

$ terraform apply -auto-approve

Error: Invalid index

  on main.tf line 42, in output "database_name":
  42:   value = mysql_database.this[0].name
    |----------------
    | mysql_database.this is empty tuple

The given key does not identify an element in this collection value.

さあ、どうしたものでしょう。

いろいろ見ていると、concatで使ってなんとかするみたいですね。

concat - Functions - Configuration Language - Terraform by HashiCorp

output "database_name" {
  value = concat(mysql_database.this.*.name, [null])[0]
}

### もしくは
output "database_name" {
  value = concat(mysql_database.this.*.name, [""])[0]
}

コミュニティモジュールとか見ていると、後者([""]指定)の方を使うみたいですね。

これで、最初のlistが空の時は、後続のダミーのlistの要素が使われるという感じに。

細工が必要ですねぇ。

for_eachで実現する

for_eachで実現する場合は、こんな感じでしょうか。

locals {
  create = true
}

resource "mysql_database" "this" {
  for_each = local.create ? toset(["my_database"]) : toset([])

  name = each.value
}

output "database_name" {
  value = concat(values(mysql_database.this)[*].name, [null])[0]

  # もしくは
  #value = concat(values(mysql_database.this)[*].name, [""])[0]
}

単一のリソースを作るかどうか制御するのが悩みどころだったのですが、こんな感じにすればできるみたいなので、
覚えておきましょう。