Fluentdサイドカーコンテナでログを別サーバーに転送する

# 概要
openshift上で稼働するアプリケーションのログをfluentdサイドカーコンテナで取得し、ログサーバーに転送する検証を実施したのでその際のメモをまとめました。

### Fluentdとは
Fluentdはオープンソースのデータコレクターです。主な機能は以下の通りです。

* Unified Logging with JSON
    * Fluentdは、データを可能な限りJSONとして構造化します。これによりFluentdは、ログデータ処理のすべての側面（複数のソースと宛先にわたるログの収集、フィルタリング、バッファリング、出力）を単一化しています。 JSONを使用すると、柔軟なスキーマを維持しながらアクセスできるため、ダウンストリームのデータ処理が簡単になります。
* Pluggable Architecture
    * Fluentdには、柔軟なプラグインシステムがあります。コミュニティが提供する500以上のプラグインは、数十のデータソースとデータ出力を接続可能にします。
* Minimum Resources Required
    * FluentdはC言語とRubyの組み合わせで書かれており、必要なシステムリソースはごくわずかです。
* Built-in Reliability
    * Fluentdは、ノード間のデータ損失を防ぐために、メモリベースおよびファイルベースのバッファリングをサポートしています。 

参考：[What is Fluentd?](https://www.fluentd.org/architecture)

### 作業の流れ
1.ログサーバーへのFluetndの導入
2.Fluentdコンテナ・イメージの作成
3.サンプルアプリのコンテナ・イメージの作成(Optional)
4.ROKSクラスターにデプロイ
5.ログサーバーにおけるFluentdの設定

### 作業の前提
* 本検証ではFluentdサイドカー用のコンテナと、Fluentdサイドカーでログを取得するためのサンプルアプリ用コンテナを作成し、1つのpodに入れてデプロイしています。



### 1.ログサーバーへのFluetndの導入
* 以下から対応する環境に合わせて導入します。
    * [Download Fluentd](https://www.fluentd.org/download)
* 本検証時は以下のガイドに従い導入しました。
    * [Install by RPM Package (Red Hat Linux)](https://docs.fluentd.org/installation/install-by-rpm)
    
### 2.Fluentdコンテナ・イメージの作成
* Fluentd公式サイトに記載のGitHubより、以下のファイルを手元にダウンロードし、全てのファイルをDocekrfileと同じ階層に配置します。
    * https://github.com/fluent/fluentd-docker-image
        * Dockerfile
        * fluent.conf
        * entrypoint.sh
* Dockerfileを編集します。
    * コンテナ起動時の権限エラー回避のため、下から7行目に以下を追記します。
        * RUN ["chmod", "+x", "/bin/entrypoint.sh"]

```yaml:Dockerfile

# AUTOMATICALLY GENERATED
# DO NOT EDIT THIS FILE DIRECTLY, USE /Dockerfile.template.erb

FROM alpine:3.9
LABEL maintainer "Fluentd developers <fluentd@googlegroups.com>"
LABEL Description="Fluentd docker image" Vendor="Fluent Organization" Version="1.10.4"

# Do not split this into multiple RUN!
# Docker creates a layer for every RUN-Statement
# therefore an 'apk delete' has no effect
RUN apk update \
 && apk add --no-cache \
        ca-certificates \
        ruby ruby-irb ruby-etc ruby-webrick \
        tini \
 && apk add --no-cache --virtual .build-deps \
        build-base linux-headers \
        ruby-dev gnupg \
 && echo 'gem: --no-document' >> /etc/gemrc \
 && gem install oj -v 3.8.1 \
 && gem install json -v 2.3.0 \
 && gem install async-http -v 0.50.7 \
 && gem install ext_monitor -v 0.1.2 \
 && gem install fluentd -v 1.10.4 \
 && gem install bigdecimal -v 1.4.4 \
 && apk del .build-deps \
 && rm -rf /tmp/* /var/tmp/* /usr/lib/ruby/gems/*/cache/*.gem

RUN addgroup -S fluent && adduser -S -g fluent fluent \
    # for log storage (maybe shared with host)
    && mkdir -p /fluentd/log \
    # configuration/plugins path (default: copied from .)
    && mkdir -p /fluentd/etc /fluentd/plugins \
    && chown -R fluent /fluentd && chgrp -R fluent /fluentd


COPY fluent.conf /fluentd/etc/
COPY entrypoint.sh /bin/

RUN ["chmod", "+x", "/bin/entrypoint.sh"]

ENV FLUENTD_CONF="fluent.conf"

ENV LD_PRELOAD=""
EXPOSE 24224 5140

USER fluent
ENTRYPOINT ["tini",  "--", "/bin/entrypoint.sh"]
CMD ["fluentd"]
```

* イメージをビルドします。
    * Dockerfileの配置してあるディレクトリで以下を実行します。

```
$ docker build -t fluentd .
```

* ローカルにイメージが作成されたことを確認します。

```
$ docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED              SIZE
fluentd                           latest              a91e23972d8d        38 seconds ago      38.7MB
```


* コンテナが起動できることを確認します。

```
$ docker run fluentd
```

* コンテナへタグ付けを行います。
    * Container Registryに合わせて実施します。
    * $ docker tag (現在のイメージ名): (ver) (ContainerRegistryパス)/(namespace)/(イメージ名):(ver)

```
$ docker tag fluentd:latest us.icr.io/test/fluentd:v1
```

* DockerイメージをContainer RegistryにPushします。
    * $ docker push <タグ付けしたイメージ名>

```
$ docker push us.icr.io/test/fluentd:v1
```

* Container RegistryにイメージがPushできたことを確認します。
    * 以下はIBM Cloud Container Registryの場合の確認方法です。それぞれのContainer Registryに合わせて確認を実施します。

```
$ ibmcloud cr image-list | grep fluentd
```

### 3.サンプルアプリのコンテナ・イメージの作成(Optional)
* 検証用のサンプルアプリとしてubuntuコンテナを用意します。別のアプリコンテナがある場合はそちらを使用してください。

* Dockerfileを用意します。

```yaml:Dockerfile
FROM ubuntu:18.04

RUN apt-get update \
    && apt-get install -y \
    wget \
    curl \
    && rm -rf /var/lib/apt/lists/*

RUN apt-get update \
    && apt-get upgrade -y
```

* イメージをビルドします。
    * Dockerfileの配置してあるディレクトリで以下を実行します。

```
$ docker build -t ubuntu-sample .
```

* ローカルにイメージが作成されたことを確認します。

```
$ docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED              SIZE
ubuntu-sample                     latest              91245ab45e54        40 seconds ago      174MB
```


* コンテナが起動できることを確認します。

```
$ docker run -it ubuntu-sample /bin/bash
```

* コンテナへタグ付けを行います。

```
$ docker tag ubuntu-sample:latest us.icr.io/test/ubuntu-sample:v1
```

* DockerイメージをContainer RegistryにPushします。

```
$ docker push us.icr.io/test/ubuntu-sample:v1
```

* Container RegistryにイメージがPushできたことを確認します。

```
$ ibmcloud cr image-list | grep ubuntu-sample
```

### 4.ROKSクラスターにデプロイ
参考：[イメージ・プル・シークレットを使用してデフォルト以外の OpenShift プロジェクトから他の IBM Cloud アカウントまたは外部プライベート・レジストリーにあるイメージにアクセスする方法](https://cloud.ibm.com/docs/openshift?topic=openshift-registry#other)

* ROKSへログインし、namespaceを作成します。

```
$ oc login
```
```
$ oc new-project <Project名>
```
 
以下「＊＊＊＊＊」で区切った手順については該当する場合のみ実施します。
＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊

＊イメージを外部Container Registryに配置している場合のみ以下のsecretの作成手順を実施します。内部レジストリ利用の場合は省略してください。

* イメージを外部Container RegistryからOCP上にpullするためのSecretを作成します。
    * default namespaceにあるsecretをコピーし新たなsecretを作成する手順を記載しています。
    
```
$ oc get secrets -n default | grep icr
```
* us-icr.io接続用Secretを作成します
    * $ oc get secret default-us-icr-io -n default -o yaml | sed 's/default/(namespace)/g' | oc -n (namespace) create -f -

```
$ oc get secret default-us-icr-io -n default -o yaml | sed 's/default/test/g' | oc -n test create -f -
```
* secretが作成できたことを確認します

```
$ oc get secrets -n (namespace)
```

＊アプリケーションがroot起動の場合は、ServiceAccountを作成し、権限を付与します。

参考：[SCC (Security Context Constraints) の管理](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/3.11/html/cluster_administration/admin-guide-manage-scc)

* ServiceAccount用ファイルを作成します。

```fluentd-sidecar-sa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd-sidecar-role
  namespace: test
  labels:
    k8s-app: ubuntu-sample
    version: v1
```

* ServiceAccountを適用します。

```
$ oc create -f fluentd-sidecar-sa.yaml
```
* ServiceaccountにSCCを付与します。
    * oc adm policy add-scc-to-user (scc_name) (user_name)

```
$ oc adm policy add-scc-to-user privileged system:serviceaccount:test:fluentd-sidecar-role
```
* SCCを確認します。(Access>Usersに追記されたことを確認)

```
$ oc describe scc privileged
```

* Serviceaccountにimage pull用のsecret情報を追加します。
    * oc patch -n <namespace名> serviceaccount/<serviceAccount名> \
-p '{"imagePullSecrets":[{"name": "<secret名>"}]}'

```
$ oc patch -n test serviceaccount/fluentd-sidecar-role -p '{"imagePullSecrets":[{"name": "test-us-icr-io"}]}'
```

* image pull用のsecretの情報が追加されたことを確認します。

```
$ oc describe serviceaccount fluentd -n (namespace名)
```

＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊

* Configmapを作成します。
    * Fluentdの設定ファイルの詳細なオプションについてはFluentd公式マニュアルを参照してください。

```fluentd-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-sidecar-cm
  namespace: test
data:
  fluent.conf: |
    <source>  
      @type tail  
      path /fluentd/log/*.log  
      pos_file /test/log/ubuntu-sample.pos   
      tag test.log   
      read_from_head true   
      format none  
      time_format %Y-%m-%dT%H:%M:%S.%NZ   
    </source>

    <filter **>  
      @type record_transformer
      <record>  
        cluster_name "#{ENV['CLUSTER_NAME']}"  
        node_name "#{ENV['NODE_NAME']}" 
        pod_name "#{ENV['POD_NAME']}"
        namespace "#{ENV['POD_NAMESPACE']}"
        pod_ip "#{ENV['POD_IP']}"
        pod_service_account "#{ENV['POD_SERVICE_ACCOUNT']}"
      </record>
    </filter>

    <match **> 
      @type forward
      @id out_fwd
      <server>  
        host "#{ENV['FLUENT_FOWARD_HOST']}"  
        port "#{ENV['FLUENT_FOWARD_PORT']}"  
      </server>
    </match>
```
* ConfigMap用yamlファイルを適用します。

```
$ oc create -f fluentd-configmap.yaml
```

* Deploymentを作成します。
    * 環境変数として「ログサーバーのIP」「ログサーバーのFluentdのPORT」等を取得したい場合はenvの情報を記載します。

```fluentd-sidecar-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ubuntu-fluentd-sidecar
  namespace: test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ubuntu-sample
  template:
    metadata:
      labels:
        app: ubuntu-sample
    spec:
      serviceAccount: fluentd-sidecar-role
      serviceAccountName: fluentd-sidecar-role
      containers:
      - name: ubuntu-sample
        image: us.icr.io/test/ubuntu-sample:v1
        volumeMounts:
        - name: varlog
          mountPath: /var/log
      - name: fluentd
        image: us.icr.io/test/fluentd:v1
        env: 
        - name: FLUENT_FOWARD_HOST 
          value: "<ログサーバーのIP>"
        - name: FLUENT_FOWARD_PORT 
          value: "<ログサーバーのFluentdのPORT>"
        - name: CLUSTER_NAME 
          value: "<クラスター名 >"
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: POD_SERVICE_ACCOUNT
          valueFrom:
            fieldRef:
              fieldPath: spec.serviceAccountName
        volumeMounts:
        - name: varlog
          mountPath: /fluentd/log
        - name: config-volume
          mountPath: /fluentd/etc
        securityContext:
          privileged: true
      imagePullSecrets:
      - name: test-us-icr-io
      volumes:
      - name: varlog
        emptyDir: {}
      - name: config-volume
        configMap:
          name: fluentd-sidecar-cm
```

* Deployment用yamlファイルを適用します。

```
$ oc create -f ubuntu-fluentd-sidecar-deployment.yaml
```

* デプロイできたことを確認します。

```
$ oc get all -n (namespace)
```

* アプリケーションのログを表示します。

```
$ oc logs (pod名) -n (namespace名) -c (container名)
```

* Fluentdサイドカーの稼働確認を行います。

```
$ oc exec (pod名) -c fluentd -n (namespace) -i -t -- /bin/sh
```


* ubuntuサイドカーの稼働確認を行います。

```
$ oc exec (pod名) -c ubuntu-sample -i -t -- bash
```


* ubuntu-sampleコンテナでログを書き出します。
    * deploymentのubuntu-sampleコンテナ定義内volumeMounts>mountPathに記載したパスにログファイルを作成しログを書き込む設定をしています。
    * ここではubuntu-sampleコンテナ内の/var/log配下にログ書き出し用のファイルを作成し書き込見ます。
    * ubuntuコンテナ内で以下を実行します。
    
```
cd /var/log
touch test.log
echo aaaaaa >> test.log
```

* FluentdサイドカーコンテナでvolumeMounts>mountPathに記載したパスで、ubuntu-sampleコンテナのファイル(test.log)が参照できることを確認します。
    * tailをしながら再度ubuntu-sampleコンテナでファイルに内容を書き込みし、ログが書き出されることを確認します。 
    * ここではfluentdコンテナ内の/fluentd/log配下に移動し、test.logファイルが参照できることを確認します。
    * fluentdコンテナ内で以下を実行します。

```
cd /fluentd/log
ls -l
tail /fluentd/log/test.log
```

### 5.ログサーバーにおけるFluentdの設定
* Fluentdサイドカーコンテナで転送したログをログサーバーのtd-agentで受け取り、ファイルに書き出す設定を行います。
* この設定ではログサーバーのFluentdが受け取ったすべてのログを1ファイルに書き出す設定を記載しています。
* ログのメッセージ内に、ログ送信元podのpod名やnamespace情報もログに書き出されるため、適宜必要な情報を抽出して確認することができます。

* ログサーバーの設定ファイル(/etc/td-agent/td-agent.conf)を編集します。

```/etc/td-agent/td-agent.conf
<source>
  @type forward 
  port 24224
  bind 0.0.0.0  
</source>

<match **>
  @type file
  path /var/log/td-agent/sidecar-test 
  append true
  <buffer>
    @type file
    path /var/log/td-agent/sidecar-test-buffer
    flush_mode interval 
    flush_interval 10s
  </buffer>
</match>
```

* 上記の設定の場合/var/log/td-agent/sidecar-test.YYYYMMDD.log形式で1日毎にファイルが追加作成されます。
* バッファーファイルのディレクトリは事前に作成しておく必要があります。(権限は755 td-agent:td-agent)

* Fluentdを再起動します。

```
$ systemctl stop td-agent
$ systemctl start td-agent
```

* 稼働確認を行います。
* サンプルアプリコンテナにログインしてログを書き出します。
* ログサーバーにログインし、ログファイル(/var/log/td-agent/XXXXXXX.log)にログが書き出されていることを確認します。
* ログ転送が成功した場合、ログサーバー側/var/log/td-agent/XXXXXXX.logファイルにログが出力されていることを確認できます。