6. 構成について

6.1. ログの集約

6.1.1. 概要

システム運用の場面では、システムの稼働状況をモニタリングする手段としてログファイルを監視することがあります。 しかしながら、WebOTX MicroService のようにコンテナベースのアプリケーションは独立したコンテナ空間で動作し、 スケールイン・スケールアウトの契機でコンテナが増減するため、従来のログ監視ソフトウェアを用いてログファイルを 直接監視することは困難です。

そこで、 WebOTX MicroService においてELKスタックを構築して 個別のコンテナで動作する WebOTX のログを1箇所に集約する方法を説明します。

ELKスタックは以下のソフトウェアを組み合わせて、ノードに点在する情報(例えばログ)を1箇所に集約する構成です。

Elasticsearch にログを集約することで、多数のコンテナで生成されたログの監視や可視化が容易になります。 また、ログファイルを収集する Logstash は定義によって Elasticsearch 以外のプロトコルやストレージへの保管が可能です。 各ソフトウェアの詳細については公式サイトを参照ください。

本節では、Logstash で WebOTX のログを収集し、事前に構築済みの Elasticsearch に送信する方法を説明します。

6.1.2. 構成

一般的にコンテナベースのアプリケーションのログ収集は、アプリケーションの実装に応じて次のいずれかの手段を用います。

(構成1) アプリケーションがログをコンテナに標準出力に出力し、ホストマシンのLogstashでそれを収集する

(構成2) アプリケーションがコンテナ内部のファイルにログを出力し、コンテナ内部のLogstashがそれを収集する

前者は構成がシンプルになるメリットがありますが、アプリケーションが複数のログファイルを出力する場合は対応できません。 WebOTX MicroService は複数のログファイルを出力するため、後者の構成を採用します。

WebOTX MicroService におけるログ収集の構成を次の図に示します。

この構成では WebOTX が出力する各種ログが更新される度に、 Logstash がログメッセージを読み取り Elasticsearch に送信します。 初期設定では個々のコンテナは別のコンテナのファイルシステムにアクセスできませんので、 Pod には WebOTX のログ出力先ディレクトリ /opt/WebOTX/domains/domain1/logsを共有ディレクトリとして扱う設定を行い、 Logstash コンテナが WebOTX コンテナのログファイルにアクセスできるようにします。

Elasticsearch に送られたログは監視や Kibana を通して WebUI でログメッセージを確認することができます。

6.1.3. 構築手順

Logstash イメージの作成

最初に、WebOTX のコンテナと同じPodで同居する Logstash のコンテナイメージを作成します。

ここでは、作業用のディレクトリを作成して、その中に次の3つのファイルを用意します。

  1. Dockerfile : Logstash の Dockerfile

  2. logstash.conf : Logstash のログ収集設定

  3. webotx : WebOTX のログファイルのパースパターン

それぞれ以下の内容を定義します。

Dockerfile

FROM docker.elastic.co/logstash/logstash-oss:6.6.0

COPY logstash.conf /usr/share/logstash/pipeline/logstash.conf
COPY webotx /usr/share/logstash/config/pattern

logstash.conf

input {
  file{
    path=>["/opt/WebOTX/domains/domain1/logs/agent.log",
            "/opt/WebOTX/domains/domain1/logs/agent.log.*",
            "/opt/WebOTX/domains/domain1/logs/server.log",
            "/opt/WebOTX/domains/domain1/logs/server.log.*"]
    start_position=>"beginning"
    sincedb_path => "/opt/WebOTX/domains/domain1/logs/sincedb"
    codec => multiline {
      patterns_dir => ["/usr/share/logstash/config/pattern"]
      pattern => "^%{L4J2_DATE}"
      negate => true
      what => "previous"
      auto_flush_interval => 10
    }
  }

  file{
    path=>["/opt/WebOTX/domains/domain1/logs/server_access.log",
            "/opt/WebOTX/domains/domain1/logs/server_access.log.*",
            "/opt/WebOTX/domains/domain1/logs/diagnostics/report.csv",
            "/opt/WebOTX/domains/domain1/logs/diagnostics/report.csv.*",
            "/opt/WebOTX/domains/domain1/logs/diagnostics/optional-stats.csv",
            "/opt/WebOTX/domains/domain1/logs/diagnostics/optional-stats.csv.*" ]
    start_position=>"beginning"
    sincedb_path => "/opt/WebOTX/domains/domain1/logs/sincedb"
  }
}

filter {
  mutate {
    add_field => {
      "ip" => "${POD_IP:unkown}"
      "node" => "${NODE_NAME:unkown}"
    }
  }

  if [path] == "/opt/WebOTX/domains/domain1/logs/agent.log" 
    or [path] == "/opt/WebOTX/domains/domain1/logs/agent.log.1" 
    or [path] == "/opt/WebOTX/domains/domain1/logs/agent.log.2" {
    grok {
      patterns_dir => ["/usr/share/logstash/config/pattern"]
      match => {
        "message" => "%{AGENT_LOG}"
      }
      overwrite => [ "message" , "timestamp"]
    }
  }

  if [path] == "/opt/WebOTX/domains/domain1/logs/server.log" 
    or [path] == "/opt/WebOTX/domains/domain1/logs/server.log.1" 
    or [path] == "/opt/WebOTX/domains/domain1/logs/server.log.2" {
    grok {
      patterns_dir => ["/usr/share/logstash/config/pattern"]
      match => {
        "message" => "%{SERVER_LOG}"
      }
      overwrite => [ "message" , "timestamp"]
    }
  }

  if [path] == "/opt/WebOTX/domains/domain1/logs/server_access.log"
    or [path] == "/opt/WebOTX/domains/domain1/logs/server_access.log.1" 
    or [path] == "/opt/WebOTX/domains/domain1/logs/server_access.log.2" {
    grok {
      patterns_dir => ["/usr/share/logstash/config/pattern"]
      match => {
        "message" => "%{ACCESS_LOG}"
      }
      overwrite => ["timestamp"]
    }
  }

  if [path] == "/opt/WebOTX/domains/domain1/logs/diagnostics/report.csv"
    or [path] == "/opt/WebOTX/domains/domain1/logs/report.csv.1" 
    or [path] == "/opt/WebOTX/domains/domain1/logs/report.csv.2" {
    if [message] =~ /Date,.+/ {
      drop { }
    }
    grok {
      patterns_dir => ["/usr/share/logstash/config/pattern"]
      match => {
        "message" => "%{DIAGNOSTIC_REPORT_LOG}"
      }
      overwrite => ["timestamp"]
    }
  }
}

output {
  stdout {
  }

  ### To send the log to Elasticsearch, please uncomment and enter Elasticsearch's host name.
  # elasticsearch {
  #   hosts => ["localhost:9200"]
  #   index => "webotx_agent"
  # }
}

Memo
上記の設定では logstash.conf の output 定義により、 Logstash は集約したログを標準出力にします。
Elasticsearch 定義のコメントを除外して、構築済みの Elasticsearch の適切な接続情報を定義してください。

webotx

## WebOTX grok pattern file.

# Common pattern.
NULLABLE_NUMBER (%{NUMBER}|-)
IP_HOSTNAME     (%{IP}|%{HOSTNAME})
MILLISECOND     \d\d\d
L4J2_DATE       %{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{HOUR}:%{MINUTE}:%{SECOND},%{MILLISECOND}
DIAGNOSTIC_DATE %{YEAR}/%{MONTHNUM}/%{MONTHDAY} %{HOUR}:%{MINUTE}:%{SECOND}.%{MILLISECOND}
AS_LOGLEVEL     (ERROR|WARN|SLOGINFO|INFO|CONFIG|DEBUG|DETAIL|TRACE)
NULLABLE_USER   (%{USER}|-)
DATE            %{MONTHDAY}/%{MONTH}/%{YEAR}
DATETIME        %{DATE:date}:%{TIME:time}
FULL_DATETIME   %{DATETIME:datetime} %{INT}
HTTP_METHOD     (OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT|PRI)
URI_STRING      [:/?#@$&'()*+,;=a-zA-Z0-9.~_%\[\]\-]+
HTTP_VERSION    HTTP/%{NUMBER:httpversion}
QUERY_STRING    (\?%{URI_STRING:querystring})?

# Log file specific pattern.
AGENT_LOG             %{L4J2_DATE:timestamp} %{AS_LOGLEVEL:level}\s*%{NOTSPACE:component}\s*- %{GREEDYDATA:text} \[%{GREEDYDATA:thread}\]%{GREEDYDATA:exception}
SERVER_LOG            %{L4J2_DATE:timestamp} - %{GREEDYDATA:text} \[%{GREEDYDATA:thread}\]
ACCESS_LOG      %{IP:clientip} - %{NULLABLE_USER:auth} \[%{FULL_DATETIME:timestamp}\] \"%{HTTP_METHOD:method} %{URI_STRING:request} %{QUERY_STRING} %{HTTP_VERSION}\" %{NULLABLE_NUMBER:status} %{NULLABLE_NUMBER:bytes} %{NUMBER:elapsed}
DIAGNOSTIC_REPORT_LOG %{DIAGNOSTIC_DATE:timestamp},%{NUMBER:average_response_time},%{NUMBER:cpu_usage},%{NUMBER:heap_used}, %{NUMBER:non_heap_used},%{NUMBER:web_threads},%{NUMBER:web_busy_threads},%{NUMBER:ap_threads},%{NUMBER:ap_busy_threads},%{NUMBER:jdbc_connections},%{NUMBER:disk_usage}

全てのファイルが準備できたら、oc コマンドを使って OpenShift 上に logstash イメージをビルドします。

> oc new-build . --to=webotx-micro-logstash
--> Found Docker image 8f45a77 (3 weeks old) from docker.elastic.co for "docker.elastic.co/logstash/logstash:6.6.0"

    * An image stream will be created as "logstash:6.6.0" that will track the source image
    * A Docker build using binary input will be created
      * The resulting image will be pushed to image stream "webotx-micro-logstash:latest"
      * A binary build was created, use 'start-build --from-dir' to trigger a new build

--> Creating resources with label build=webotx-micro-logstash ...
    imagestream "logstash" created
    imagestream "webotx-micro-logstash" created
    buildconfig "webotx-micro-logstash" created
--> Success

> oc start-build webotx-micro-logstash -n <プロジェクト名> --from-dir=.
Uploading directory "." as binary input for the build ...
build "webotx-micro-logstash-2" started

ビルドの状況は oc get builds コマンドで確認ができます。

> oc get builds
NAME                       TYPE      FROM      STATUS                       STARTED         DURATION
webotx-micro-logstash-1    Docker    Binary    Running                      7 seconds ago

ビルドが完了すると OpenShift のイメージストリームにイメージが登録されます。 oc get is コマンドを実行して webotx-micro-logstash が登録されていることを確認します。

> oc get is
NAME                    DOCKER REPO                                                    TAGS      UPDATED
rhel                    docker-registry.default.svc:5000/<プロジェクト名>/rhel                    latest    2 months ago
webotx-micro            docker-registry.default.svc:5000/<プロジェクト名>/webotx-micro            latest    3 hours ago
webotx-micro-logstash   docker-registry.default.svc:5000/<プロジェクト名>/webotx-micro-logstash   latest    1 minutes ago
WebOTX と Logstash の Pod 作成

OpenShift Webコンソールにログインし、 作業中のプロジェクトを選択して、 Overview 画面に移動します。 画面右上の "Add to Project" から "Import YAML/JSON" を選択して、以下の定義を入力します。

apiVersion: v1
kind: DeploymentConfig
metadata:
  name: webotx-pod
spec:
  template:
    metadata:
      labels:
        name: webotx-pod
        app: webotx-pod
    spec:
      volumes:
      - name: shared-data
        emptyDir: {}
      containers:
      - name: webotx-micro
        image: docker-registry.default.svc:5000/<プロジェクト名>/webotx-micro:latest
        volumeMounts:
        - name: shared-data
          mountPath: /opt/WebOTX/domains/domain1/logs
      - name: webotx-micro-logstash
        image: docker-registry.default.svc:5000/<プロジェクト名>/webotx-micro-logstash:latest
        volumeMounts:
        - name: shared-data
          mountPath: /opt/WebOTX/domains/domain1/logs
        env:
          - 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
  replicas: 1
  selector:
    name: webotx-pod
    app: webotx-pod

各属性の概要は以下の通りです。 spec.template.spec.containers.image に定義するイメージ名を変更するなど、 環境や運用に合わせて適宜変更してください。

属性 概要

metadata.name

Podの名称

spec.template.metadata.labels

任意のメタ情報

spec.template.spec.volumes

Pod内のコンテナ同士でログファイルを共有するためのストレージ定義

spec.template.spec.containers.name

コンテナ名

spec.template.spec.containers.image

コンテナイメージ名

spec.template.spec.containers.volumeMounts

Pod内のコンテナ同士で共有するファイルパス

spec.template.spec.containers.env

コンテナ内部で有効な環境変数の定義

spec.replicas

起動するPodの数

以上で設定は終了です。 webotx-pod を起動すると WebOTX のログは出力の都度 logstash.conf に指定した Elasticsearch に転送されます。

6.2. 障害発生時の対処

6.2.1. MicroProfile Health Check との連携

ここでは、Microprofile Health Check との連携の例として、OpenShift 上で配備したマイクロサービスの /health にリクエストをして、コンテナの監視を行う方法について説明します。

OpenShift 上に配備したアプリケーションは、OpenShift のコンソールから以下のように確認できます。

(1)OpenShift コンソールより、「Application」-「Deployments」を開く

(2)配備したアプリケーションの名前のリンクをクリック

(3)右上にある「Actions」ボタンから「Edit Health Checks」を選択

(4)「Add Liveness Probe」のリンクをクリックして、/health を設定

以下のように設定して、Save ボタンをクリックします。

Type HTTP GET
Path /health
Port 80
Initial Delay 30

Initial Delay は Pod が起動してから /health にリクエストを行うまでの待ち時間です。 ここでは、例として 30 秒を指定しています。 この待ち時間を指定しなかった場合、WebOTX のドメインが起動していない状態で /health へ リクエスト行うため、Pod 起動時に以下のような WARN がイベントログに記録されることが あります。

      Unhealthy    
      Liveness probe failed: Get http://10.128.0.12:80/health: dial tcp 10.128.0.12:80: getsockopt: connection refused

以下は、OpenShift の Health Check の設定編集画面です。

上記の設定により、/health の応答が DOWN となっている場合に Pod が自動的に停止し、 新しく Pod が起動するようになります。