OpsWorks上でfluentdでログ収集してBigQueryに投入する方法要点まとめ

## この記事の要点

- OpsWorksでfluentdをインストール・設定する方法
- IPアドレスを固定できない中、どうやって転送先のサーバを指定するか
- Ruby on Rails 4.2でfluentdにログを送るには

## 方針

- VPC使います。
- 手順はカスタムCookbookでコード化する
- 設定の使い分け
  - カスタムJSON
     - 環境によって変わる値（ステージングか、本番かなど）
  - カスタムCoookbookの`opsworks/attributes/default.rb`
     - 決め打ちしたい設定値
  - Appsの環境変数（Environment Variables）
     - Railsアプリの設定、秘密情報など

## 前提: カスタムCookbook

設定操作などをコード化するためにカスタムCookbookを使います。

https://docs.aws.amazon.com/ja_jp/opsworks/latest/userguide/workingcookbook.html

### カスタムCookbookの設定について

スタック設定で`Use custom Chef cookbooks`を`Yes`にします。
リポジトリにGitHubを使う場合は`ssh://git@ssh.github.com:443/hoge/fuga.git`のようにします。普通外向きの22番ポートは開けないので443番ポートを使います。

また、カスタムCookbookは1つしか指定できないので、ほかのCookbookを使えるようにするために、`Manage Berkshelf`を`Yes`にしておきます。
こうすることでBerksfileに記載したCookbookを使えるようになります。

## OpsWorksでfluentdをインストールする

[公式のCookbook](https://github.com/treasure-data/chef-td-agent)を使います。

これを使うことで、fluentdのインストールだけでなく、設定もChefのレシピで書けるようになります。

### サーバーとクライアント

fluentdでログを収集する場合、ログを集約するサーバーと、自ホスト内のログをとりまとめてサーバーに送るクライアント（？という表現でよいでしょうか）を用意するのが一般的かと思います。

OpsWorksでサーバーとクライアントそれぞれの設定を行うために、今回はレイヤーを使うことにしました。

具体的には`Fluentd Server Layer`を用意し、そのレイヤーが設定されたインスタンスにはサーバー向けの設定、その他のインスタンスではクライアント向けの設定を行うようなレシピを書くことにしました。

### BigQueryに蓄積する

[fluent-plugin-bigquery](https://github.com/kaizenplatform/fluent-plugin-bigquery)を使いました。

それとは別に

- APIの利用登録
- bqコマンドでデータセットの作成
- Google API ConsoleでGCEサービスアカウントJSONキーの取得

など行っておきます。

## サーバーの指定

プライベートIPアドレスを固定できればそれで指定してやるのが楽ですが、OpsWorksでは指定できません。そこで、

- Route53のPrivate DNS機能をVPC内で名前解決できるようにする
- インスタンス起動時に自身のホスト名とプライベートIPアドレスをRoute53のAレコードに登録

とすることにしました。
こうすることでサーバーの指定を`log.stackname.yourdomain.local`のような形で設定に記述でき、プライベートIPアドレスが変化しても自動的に追従できます。

VPCでPrivate DNSを使うには以下の記事が参考になるので適当に設定します。

[Route53編～Private DNS～ | ナレコムAWSレシピ](http://recipe.kc-cloud.jp/archives/7545)

OpsWorksで起動時にRoute53のAレコードを設定するには、[route53](https://supermarket.chef.io/cookbooks/route53)を使って自分で書くか、あるいは[opsworks_route53](https://github.com/verdigris-cookbooks/opsworks-route53)などを使えば簡単です。

なお、インスタンスのIAM RoleにRoute 53の設定を触れる権限が必要です。（通常  aws-opsworks-ec2-role に AmazonRoute53DomainsFullAccess AmazonRoute53FullAccess あたりを設定すればとりあえず動くはずです。）

## Ruby on Rails 4.2でfluentdにログを送る

[act-fluent-logger-rails](https://github.com/actindi/act-fluent-logger-rails)を使いました。

Railsからはローカルホストのfluentdにしかるべきタグを付けて送るだけです。



## 実装

### カスタムJSON（Stack）

```json
{
  "fluentd_server":{
    "gce_project_id": "BigQuery使うプロジェクトID",
    "gce_service_json": "サービスアカウントJSONキーの内容",
    "host":"log-server1.stackname.yourprivatedomain.local",
    "port":24224
  },
  "opsworks_route53":{
    "domainname":"yourprivatedomain.local",
    "prepend_stack_name":true,
    "zone_id":"XXXXXXXXXXXXX"
  }
}
```

setup等のレシピ内で使用する設定で、`opsworks/attributes/default.rb`に含められない内容をここで設定します。

### カスタムCookbook

今回は

- Setup opsworks::setup
- Configure opsworks::configure
- Deploy opsworks::deploy

を使いました。

#### Berksfile

```rb
source 'https://supermarket.chef.io'

# (snip)

cookbook 'td-agent', '~> 2.2.0'
cookbook 'opsworks_route53', git: 'https://github.com/verdigris-cookbooks/opsworks-route53.git'
```

`td-agent`および`opsworks_route53`クックブックを使うのでその指定を追加しました。

#### opsworks/metadata.rb

```rb
name             'opsworks'
maintainer       'Yuichi Takeuchi'
maintainer_email 'info@takeyu-web.com'
license          'All rights reserved'
description      'Installs/Configures base'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version          '0.1.0'
depends          'td-agent'            # これと
depends          'opsworks_route53'    # これ
```

dependsを指定してデフォルトのattributesを読み込むようにします。

#### opsworks/attributes/default.rb

```rb
default[:td_agent][:plugins] = [
    'bigquery'
]
default[:td_agent][:includes] = true
```

`fluent-plugin-bigquery`を使うのでその指定と、追加の設定を`/etc/td-agent/conf.d`に置く設定を追加しました。この設定については[このへん](https://github.com/kaizenplatform/fluent-plugin-bigquery)。

#### opsworks/recipes/setup.rb

```rb
# (snip)

# fluentd server and client
include_recipe 'td-agent'
is_log_server = node[:opsworks][:instance][:layers].select { |layer| layer.match(/\Afluentd-server\z/) }.any?
if is_log_server
  td_agent_match 'myapp' do
    type 'bigquery'
    tag 'myapp'
    params(
        method: 'insert',
        auth_method: 'json_key',
        json_key: '/etc/td-agent/service_account.json',
        project: node[:fluentd_server][:gce_project_id],
        dataset: 'myapp',
        auto_create_table: true,
        table: 'rails_%Y%m%d',
        time_format: '%s',
        time_field: 'time',
        field_integer: 'time',
        field_string: 'messages,ip,ua,uid'
    )
  end

  file '/etc/td-agent/service_account.json' do
    action :create
    mode '0600'
    owner 'td-agent'
    group 'td-agent'
    content node[:fluentd_server][:gce_service_json]
  end
else
  td_agent_match 'myapp' do
    type 'forward'
    tag 'myapp'
    params(
        method: 'insert',
        flush_interval: '60s',
        send_timeout: '60s',
        heartbeat_type: 'udp',
        heartbeat_interval: '1s',
        server: [
            {
                host: node[:fluentd_server][:host],
                port: node[:fluentd_server][:port]
            }
        ],
        secondary: [
            {
                type: 'file',
                path: '/var/log/td-agent/forward-failed'
            }
        ]
    )
  end
end
```

インスタンスセットアップ時にfluentdをインストールして、インスタンスがFluentd Serverレイヤーかどうかでそれぞれサーバ向けの設定とクライアント向けの設定ファイルを作ります。

#### opsworks/recipes/configure.rb

```rb
# (snip)

include_recipe 'opsworks_route53'
```

`opsworks_route53::default`を実行するだけで、`インスタンス名.スタック名.ドメイン名`（例 `log-server1.stackname.yourprivatedomain.local`）をRoute53にAレコードを登録してくれます。便利。

##### opsworks/recipes/deploy.rb

```rb
# (snip)

node[:deploy].each do |application, deploy|
  next unless deploy[:application_type] == 'rails'

  template "#{deploy[:deploy_to]}/current/config/fluent-logger.yml" do
    source 'fluent-logger.yml.erb'
    action :create
    mode '0660'
    owner deploy[:user]
    group deploy[:group]
    variables(
        rails_env: deploy[:rails_env]
    )
  end
end
```

Railsアプリのデプロイの際に、[act-fluent-logger-rails](https://github.com/actindi/act-fluent-logger-rails)の設定を置くことにしました。

#### opsworks/templates/default/fluent-logger.yml.erb

```erb
<%= @rails_env %>:
  fluent_host:   '127.0.0.1'
  fluent_port:   24224
  tag: 'myapp'
  messages_type: 'string'
```