アクセスログ

アクセスログ #

Webフロントエンドが管理するサーバーにおける最重要なシステムの一つはアクセスログです。 不正アクセスなどのセキュリティ的な側面や、会社の収益のツリー構造に関わる部分など多くの重要な情報をここから得られます。 ゆえにこの部分のシステムは信頼度が最も高い方法で実現する必要があります。

したがって移行前のアーキテクチャをなるべく踏襲しつつ、Ingress Gatewayに近いところに配置する必要がありました。 また、ログは既存のfluentdの収集と連携する必要がありました。

最終的に本番で稼働しているアーキテクチャは次のようになります。

Ingress Gatewayとアクセスログ周りのアーキテクチャ #

アクセスログの収集アーキテクチャ

戦略としてはIngress Gatewayの前段にnginxを配置し、クラスター外からのアクセスを最初にnginxが受けるようにしました。nginxから出力されるアクセスログはsyslogでUnix Socketを経由してfluent-bitに転送しています。 fluent-bitはsyslogをINPUTとして既存のfluentdと結合するために出力先のディレクトリとログの書き出しをコントロールしています。

このアーキテクチャに至った経緯を紹介します。

アクセスログの出力にnginxを利用している理由 #

今回は移行が伴っているため、なるべく低コストで移行を安全に実施したい狙いがありました。 もともとnginxからログを出力していることもあり、その実績からそのまま流用する形を取りました。

また、envoyによるアクセスログの出力も考慮に入れましたが、Cookieなどに含まれる情報を出力するためにluaを書く必要があったり、そのパース用にスクリプト自体が保守するのが大変であるため断念しました。

fluent-bitで収集してfluentdに渡している理由 #

fluentdは移行前からあるログ収集の手段です。 fluent-bitはfluentdのC言語実装で、fluent-bitも出力先をfluentdと同じ場所に向けることは可能です。 しかしながらこれも移行をスムーズに進めるために既存のfluentdの設定を頑張ってfluent-bitに移すことはしませんでした。

nginxからfluent-bitにUnix Socket経由でログを送信している理由 #

最初、fluent-bitをDaemonSetとして配置してIngress Gateway用のNodeに配置するようにしていました。 nginxのログをstdoutで出力し、/var/log/containers/[containerId].logに出力されるnginxのログをfluent-bitのtail INPUTを利用して収集していました。

しかしながら、高rps環境下でtailを利用するとfluent-bitのtailが突然止まる不具合に遭遇しました。 これはissueに起票されていますが、活発でないとしてBotによって2022/04/09クローズされました。

挙動を見ているとどうやら/var/log/containersに出力されるログファイルのシンボリックリンク先である、 /var/log/pods/[pod-id]/0.log.gzファイルにアーカイブされるときにファイルディスクリプタあたりが変更されそこでうまくfluent-bitが処理できていなさそうだということがなんとなくわかっています。 とはいえこれを修正するためにfluent-bitにPull Requestを送って、リリースされるまでの間ログが収集できないとなると移行スケジュールに問題が発生するため別の方法を考えました。

幸い、AWSのfluent-bitのトラブルシューティングがあったのでここを参考にしました。

Scalingの章に高スループットでfluent-bitを運用するための方法が紹介されており、そこに「DaemonSetモデルからSidecarモデルへ」と「ログファイルのTailからLog Streamingモデルへ」の変更が有効であることが記述されていました。

すぐにこれを採用し、最初に紹介したアーキテクチャへと変貌を遂げました

具体的な設定 #

これら理由を踏まえた上で設定は次のようになります。

nginxの出力先の設定 #

ログは取り扱いしやすいように一度JSONで出力しています。 syslogは/tmp/sidecar-nginx/sys-log.sockに対して出力しています。

log_format json_access_format escape=json '{ 中略 }'
server {
  access_log syslog:server=unix:/tmp/sidecar-nginx/sys-log.sock json_access_format;
}

fluent-bit #

INPUT/tmp/sidecar-nginx/sys-log.sockからnginxのログをJSON形式で読み込み、 syslog → JSON → 日付抽出 → タグの書き換え(出力先の調整)FILTERを通った後、 ファイルに書き出しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
[SERVICE]
    Flush               1
    Grace               120
    Daemon              off
    Parsers_File        parsers.conf
    HTTP_Server         On
    HTTP_Listen         0.0.0.0
    HTTP_PORT           2020
    Log_Level           info

[INPUT]
    Name                syslog
    Tag                 kube.*
    Path                /tmp/sidecar-nginx/sys-log.sock
    Parser              syslog-rfc3164-local
    Mode                unix_udp

[FILTER]
    Name                parser 
    Match               kube.*
    Key_Name            message
    Preserve_Key        true
    Reserve_Data        true
    Parser              json

[FILTER]
    Name                lua
    Match               kube.*
    script              create-log-file-path.lua
    call                create_log_file_path

[FILTER]
    Name                rewrite_tag
    Match               kube.*
    Rule                vhost ^(.*)$ /log/output/path/$log_file_path true

[PARSER]
    Name                nginx_access_log
    Format              regex
    Regex               ^(?<container_log_time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<message>.*)$
    Time_Key            time
    Time_Format         %Y-%m-%dT%H:%M:%S%z
    Time_Keep           On

[PARSER]
    Name                syslog-rfc3164-local
    Format              regex
    Regex               ^<(?<pri>[0-9]+)>(?<time>[^ ]* {1,2}[^ ]* [^ ]*) (?<ident>[a-zA-Z0-9_/.-]*)(?:[(?<pid>[0-9]+)])?(?:[^:]*:)? *(?<message>.*)$
    Time_Key            time
    Time_Format         %b %d %H:%M:%S
    Time_Keep           On

[PARSER]
    Name               json
    Format             json

[OUTPUT]
    Name               file
    Match              *
    Format             template
    Template           method:{method}	uri:{uri} 中略

日付順で出力するために、以下のlua scriptを利用してTagを書き換えています。

-- create-log-file-path.lua
function create_log_file_path(tag, timestamp, record)
  new_record = record
  new_record["log_file_path"] = os.date("%Y-%m%d",timestamp).."/istio-ingressgateway-access.log"
  return 1, timestamp, new_record
end

IstioOperator #

KubernetesのManifestファイルは次のようになります

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-my-ingressgateway
spec:
  profile: empty
  components:
    ingressGateways:
      - name: istio-my-ingressgateway
        enabled: true
        k8s:
          overlays:
            - apiVersion: apps/v1
              kind: Deployment
              name: istio-my-ingressgateway
              patches:
                - path: spec.template.spec.containers[1]
                  value:
                    name: sidecar-nginx
                    env:
                      - name: TZ
                        value: Asia/Tokyo
                    image: # nginx
                    securityContext:
                      privileged: true
                      runAsUser: 0
                      runAsGroup: 0
                      runAsNonRoot: false
                    volumeMounts:
                      - name: cache-volume
                        mountPath: /var/cache/nginx
                      - name: pid-volume
                        mountPath: /var/run
                      - name: ingressgateway-sidecar-nginx-conf
                        mountPath: /etc/nginx
                        readOnly: true
                      - name: nginx-unix-socket
                        mountPath: /tmp/sidecar-nginx # nginxのsyslog出力場所
                - path: spec.template.spec.containers[2]
                  value:
                    name: sidecar-fluent-bit
                    image: fluent/fluent-bit:1.8.13
                    imagePullPolicy: Always
                    ports:
                      - containerPort: 2020
                    securityContext:
                      privileged: true
                      runAsUser: 0
                      runAsGroup: 0
                      runAsNonRoot: false
                    readinessProbe:
                      httpGet:
                        path: /api/v1/metrics/prometheus
                        port: 2020
                      failureThreshold: 3
                      timeoutSeconds: 3
                    livenessProbe:
                      httpGet:
                        path: /
                        port: 2020
                      failureThreshold: 3
                      timeoutSeconds: 3
                    resources:
                      requests:
                        cpu: 150m
                        memory: 128Mi
                      limits:
                        cpu: 150m
                        memory: 128Mi
                    volumeMounts:
                      - name: sidecar-fluent-bit
                        mountPath: /fluent-bit/etc
                      - name: log-output-volume
                        mountPath: /log/output/path # fluent-bitのログの出力場所
                      - name: nginx-unix-socket
                        # fluent-bitがfluent-bitのUNIX Socketを読み込む場所
                        mountPath: /tmp/sidecar-nginx
                - path: spec.template.spec.volumes[8]
                  value:
                    name: ingressgateway-sidecar-nginx-conf
                    configMap:
                      name: ingressgateway-sidecar-nginx-conf
                      items:
                        - key: nginx.conf
                          path: nginx.conf
                - path: spec.template.spec.volumes[9]
                  value:
                    name: sidecar-nginx-error-page
                    configMap:
                      name: sidecar-nginx-error-page
                - path: spec.template.spec.volumes[10]
                  value:
                    name: cache-volume
                    emptyDir: {}
                - path: spec.template.spec.volumes[11]
                  value:
                    name: pid-volume
                    emptyDir: {}
                - path: spec.template.spec.volumes[12]
                  value:
                    name: varlog
                    hostPath:
                      path: /var/log
                - path: spec.template.spec.volumes[13]
                  value:
                    name: sidecar-fluent-bit
                    configMap:
                      name: sidecar-fluent-bit
                - path: spec.template.spec.volumes[14]
                  value:
                    name: log-output-volume
                    hostPath:
                      path:  /log/output/path
                - path: spec.template.spec.volumes[15]
                  value:
                    name: nginx-unix-socket
                    emptyDir: {}

/tmp/sidecar-nginxに対してUnix Socket用のEmpty Directoryを作成し、Pod内でシェアすることでPodとして見たときにポータビリティが確保できます。

IstioOperatorで新しくContainerやVolumeを追加する場合は現状 k8s.overlays で頑張って追加するしかありませんが、 ManifestをTypeScriptで管理しているため、管理が難しいなどの問題は発生しませんでした。

ただしバージョンアップに伴ってIstioOperatorが作成するIngressGatewayのDeploymentを確認する必要があります。 早々バージョン更新の頻度は高くないので、バージョン更新後の検証と同時にやっても問題ないでしょう。

これから #

ここまでに説明したことを改めて整理すると、移行時に飲み込んだ冗長的な部分を最適化することがまずできます。

  1. nginxのログ出力をIngress Gatewayのistio-proxyで実施する
  2. fluentdによるログ転送処理をfluent-bitに移行する

これらを実施することでIstioOperatorのManifestはある程度見やすくなります。