0. はじめに
三菱総研DCSデジタル企画推進部の山内です。最近は、クラウドネイティブなアプリケーションの基盤について調査、検証を行っています。
マイクロサービスアーキテクチャを採用したアプリケーションのモニタリング環境について検証する中で、ログ収集ツールとしてGrafana LokiというOSSに注目しました。
今回は検証環境ということでコストを抑えた構成を取ったため、その構成をご紹介します。
1. Grafana Lokiとは
Grafana Lokiはログ収集ツールで、Grafana Labs主体で開発されているOSSです。
Grafana Lokiでは、収集するログデータを保管するデータストアを2種類用意する必要がありました。
・index用:ログデータのインデックスを保管する。
・chunk用:ログデータ自体を保管する。
モニタリング対象のマイクロサービスはGKE(Google Kubernetes Engine)上で稼働しています。データストアにクラウドサービスを活用する場合、index用とchunk用の組み合わせは以下のようになります。
・index用:BigTable
・chunk用:GCS(Google Cloud Storage)
※Grafana Lokiがログ収集する仕組みを簡易的に表現しています。
BigTableの利用料としては、①クラスタ利用料と②ストレージ利用料の2種類が発生します。
仮に100GBのindexが生成されるとした場合、BigTableの利用料を試算したところ、月額7万円程度かかることがわかりました。
Grafana Lokiの検証のためだけにBigTableを利用することは費用がかかりすぎると判断し、boltdbというツールを使ってindexもGCSに保管するように構成しました。
indexの保管において、BigTableを利用した構成では約7万円かかるところ、boltdbを利用した構成ではGCSの利用料だけとなり、月額200円弱(0.016USD*100GB*110円)におさえることができます。
このように、boltdbを利用することで大幅な費用削減ができると考えたのが今回の構成の背景となります。
2. コストを抑えた構成
boltdbは組み込み型のKVS(キーバリューストア)です。boltdbを使ってindexをローカルに保持することができます。
今回はindexを一旦boltdbに保持し、boltdb-shipperを使いindexをGCSに転送することで、データ永続化を実現します。
※boltdb-shipperはGrafana LokiのV1.5.0でリリースされたオプションです。
これまでindexはCassandraやBigTableといったNoSQLデータベースを使う必要がありましたが、boltdb-shipperのおかげでchunk保管用のオブジェクトストレージにindexも保管できるようになり、環境を簡易的にしコストを削減することができるようになりました。
環境構築する手順を具体的に見ていきます。
今回は、モニタリング対象のマイクロサービスが稼働するGKE上にGrafana Lokiを相乗りさせます。
Grafana Lokiの導入にあたって、Helmを利用します。
Helmとは、Kubernetesにおけるパッケージマネージャのようなものです。
様々なKubernetesマニフェストのテンプレート(Helmチャート)がアップロードされており、コマンド操作でテンプレートに具体値を与えて、その内容でマニフェストをKubernetesクラスタへ適用できます。
Helmチャートダウンロード
Grafana LokiがGCSにアクセスする際の認証情報を設定する必要がありますが、Helmチャートがその設定に対応していないため、一部テンプレートを修正するためにHelmチャートをダウンロードします。
helm fetch loki/loki-stack
values.yamlの編集
まずはvalues.yamlを編集し、利用するデータストアを設定します。
helmテンプレートの編集
GCSにアクセスするための認証情報をsecretリソースとして定義します。
apiVersion: v1 kind: Secret metadata: name: loki-access-gcs type: Opaque data: key.json: XXXXX
定義したsecretリソースをGrafana Lokiにマウントし、認証情報を環境変数から読み出せるようにします。
apiVersion: apps/v1 kind: StatefulSet metadata: name: {{ template "loki.fullname" . }} namespace: {{ .Release.Namespace }} labels: app: {{ template "loki.name" . }} chart: {{ template "loki.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} annotations: {{- toYaml .Values.annotations | nindent 4 }} spec: podManagementPolicy: {{ .Values.podManagementPolicy }} replicas: {{ .Values.replicas }} selector: matchLabels: app: {{ template "loki.name" . }} release: {{ .Release.Name }} serviceName: {{ template "loki.fullname" . }}-headless updateStrategy: {{- toYaml .Values.updateStrategy | nindent 4 }} template: metadata: labels: app: {{ template "loki.name" . }} name: {{ template "loki.name" . }} release: {{ .Release.Name }} {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} annotations: checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} spec: serviceAccountName: {{ template "loki.serviceAccountName" . }} {{- if .Values.priorityClassName }} priorityClassName: {{ .Values.priorityClassName }} {{- end }} securityContext: {{- toYaml .Values.securityContext | nindent 8 }} initContainers: {{- toYaml .Values.initContainers | nindent 8 }} {{- if .Values.image.pullSecrets }} imagePullSecrets: {{- range .Values.image.pullSecrets }} - name: {{ . }} {{- end}} {{- end }} containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: - "-config.file=/etc/loki/loki.yaml" {{- range $key, $value := .Values.extraArgs }} - "-{{ $key }}={{ $value }}" {{- end }} volumeMounts: {{- if .Values.extraVolumeMounts }} {{ toYaml .Values.extraVolumeMounts | nindent 12}} {{- end }} - name: config mountPath: /etc/loki - name: storage mountPath: "/data" subPath: {{ .Values.persistence.subPath }} - name: loki-access-gcs # secretをボリュームマウントする mountPath: /etc/secrets ports: - name: http-metrics containerPort: {{ .Values.config.server.http_listen_port }} protocol: TCP livenessProbe: {{- toYaml .Values.livenessProbe | nindent 12 }} readinessProbe: {{- toYaml .Values.readinessProbe | nindent 12 }} resources: {{- toYaml .Values.resources | nindent 12 }} securityContext: readOnlyRootFilesystem: true env: {{- if .Values.env }} {{- toYaml .Values.env | nindent 12 }} {{- end }} {{- if .Values.tracing.jaegerAgentHost }} - name: JAEGER_AGENT_HOST value: "{{ .Values.tracing.jaegerAgentHost }}" {{- end }} - name: GOOGLE_APPLICATION_CREDENTIALS # GCS認証情報を、マウントしたsecretの内容から環境変数に設定する。 value: /etc/secrets/key.json {{- if .Values.extraContainers }} {{ toYaml .Values.extraContainers | indent 8}} {{- end }} nodeSelector: {{- toYaml .Values.nodeSelector | nindent 8 }} affinity: {{- toYaml .Values.affinity | nindent 8 }} tolerations: {{- toYaml .Values.tolerations | nindent 8 }} terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} volumes: - name: config secret: secretName: {{ template "loki.fullname" . }} - name: loki-access-gcs # secretをボリュームとして定義する secret: secretName: loki-access-gcs {{- if .Values.extraVolumes }} {{ toYaml .Values.extraVolumes | indent 8}} {{- end }} {{- if not .Values.persistence.enabled }} - name: storage emptyDir: {} {{- else if .Values.persistence.existingClaim }} - name: storage persistentVolumeClaim: claimName: {{ .Values.persistence.existingClaim }} {{- else }} volumeClaimTemplates: - metadata: name: storage annotations: {{- toYaml .Values.persistence.annotations | nindent 8 }} spec: accessModes: {{- toYaml .Values.persistence.accessModes | nindent 8 }} resources: requests: storage: {{ .Values.persistence.size | quote }} storageClassName: {{ .Values.persistence.storageClassName }} {{- if .Values.persistence.selector }} selector: {{- toYaml .Values.persistence.selector | nindent 8 }} {{- end }} {{- end }}
このHelmチャートを使って、Kubernetesマニフェストを生成します。
helm template --release-name sample --namespace monitoring loki-stack --set loki.persistence.enabled=true,loki.persistence.storageClassName=standard,loki.persistence.size=5Gi
Kubernetesマニフェスト適用
生成したマニフェストをGKEに適用し、Grafana Lokiをデプロイして完了です。
3. おわりに
Grafana Lokiは、メトリクス監視のPrometheusや分散トレーシングのJaeger、ダッシュボードツールのGrafanaと連携できることを期待して導入しています。
詳細な動きは未検証ですが、マイクロサービスのオブザーバビリティ向上にむけて、検証を進めていきたいと思います。