本記事は Kubernetes2 Advent Calendar 2020 の 5日目です。何を書くか最後まで迷ってました。
はじめに
インフラのイの字も無かった私ですが、半年前にwebサービスのインフラ開発をすることになり、そこでk8sに出会いました。インフラ構成をチームメンバーと考え実装してく中でとある問題に行きつきます。
「k8sってアプリログとかアクセスログとかどうやって取ればいいんだ?」
調べていくうちにk8sには多くのログ監視ツールや仕組みがあることを知りました。そこで今回採用したのがGrafana、loki、promtailのログ監視構成です。この構成になった理由ですが
- k8s likeなログ収集(デプロイ簡単だし。ログ取得楽ちんだし。ログ整形しなくていいし)
- Grafanaのダッシュボードがチーム内でかなり評判よかった(かっちょいい)
- 最近結構熱い(?)
という結構単純な理由です(見た目大事ですよね)。promtailやlokiに関してはhelmによるデプロイが推奨されていますし、コマンド一発でk8sにデプロイできるのでhelmを使いました。よく見るlokiのデプロイ方法だと、
# リポジトリの追加
helm repo add loki https://grafana.github.io/loki/charts
helm repo update
# lokiデプロイ(default設定)
helm upgrade --install loki --namespace={{namespace}} loki/loki
みたいな感じですよね。これ実はだいぶ罠でした(理由は後ほど)。当時はそんなことも露知らず、コンテナのデプロイやCPU、メモリ、DIskなどの監視も上手くいき、一先ずは本番ローンチできた訳です。
問題発生
運用開始からしばらくした頃、問題が発生します。数あるnodeのうちの1つのDisk使用量が90%を超えたのです。「ん?なんか一つだけ明らかに使用量多いな」と思いつつ色々調べてみると結構えげつないことが判明しました。helmでデプロイするlokiのデフォルト構成、実はPodのVolumeのタイプがemptyDirで作成されていました。
これ、具体的に何がまずいかというと、
・何かの拍子でlokiのPodが落ちた時、収集してきたログが全部消える
・ログによりnodeのストレージが圧迫される
という事態に陥ります。
原因
なぜそうなってしまっているのかというと、lokiのhelmでデプロイするときの設定ファイル、こいつのpersistenceの部分がvolumeの拡張設定になるのですが、デフォルトだと
## ref: https://kubernetes.io/docs/concepts/storage/persistent-volumes/
## If you set enabled as "True", you need :
## - create a pv which above 10Gi and has same namespace with loki
## - keep storageClassName same with below setting
persistence:
enabled: false
accessModes:
- ReadWriteOnce
size: 10Gi
annotations: {}
# selector:
# matchLabels:
# app.kubernetes.io/name: loki
# subPath: ""
# existingClaim:
という風な設定になっています。そして肝心のlokiのstatefulsetのyamlファイルですが、Volumeのログを保存する部分(name: storage)が以下のようになっています。
apiVersion: apps/v1
kind: StatefulSet
metadata:
~~
spec:
~~
template:
metadata:
~~
spec:
~~
volumes:
~~
{{- 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 }}
そうです、{{- if not .Values.persistence.enabled }}
こいつです。設定ファイル(values.yaml)のpersistenceのenabledが false
だとemptyDirでstatefulsetが作成されます。んでデフォルトは false
ですね。そういうことでした。
正しくログを保存しよう
基本的にログは勝手に消えてはならないので、PersistentVolume を使って安全に保管することが推奨されます。ですので loki の statefulset が PersistentVolume を使うような設定をしていきます。
-
StorageClass と PersistenceVolumeClaim の作成
まず PersistentVolume を作成するために StorageClass と PersistenceVolumeClaim を作成します。外部ストレージに保存できてバックアップも取れるのが割とベストかなと思い、かつ今回は AKS(Azure Kubernetes Service)を使用していたので、StorageClass の Volume タイプは Azure Files にしています。(複数マウントもできてgeoレプリケーションも組めるのでAKS使ってるなら割とおすすめ)
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: azurefile-csi-standard-retain-loki-jpeast-gzrs namespace: loki provisioner: kubernetes.io/azure-file reclaimPolicy: Retain parameters: skuName: Standard_GZRS location: japaneast --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: azure-blob-storage-gzrs namespace: loki spec: accessModes: - ReadWriteMany storageClassName: azurefile-csi-standard-retain-loki-jpeast-gzrs resources: requests: storage: 10Gi
-
loki のデフォルト構成ファイル(values.yaml)を吐き出す
次にlokiの構成を変更するために元の構成ファイルを用意します。公式の GitHubから持ってきても良いですが以下のコマンドでもファイルとして吐き出せます。
helm inspect values loki/loki > ./values.yaml
-
作成したStorageClass を values.yamlに適用
2で吐き出した構成ファイルを弄ります。persistenceの部分を以下のように変更します
persistence: enabled: true ## trueにする storageClassName: "azurefile-csi-standard-retain-loki-jpeast-gzrs" ## 作成したStorageClass名 annotations: {} existingClaim: "azure-blob-storage-gzrs" ## 1でPVCを予め作ってる場合はexistingClaimにPVC名を指定
1で PersistenceVolumeClaim を作らずにここで動的プロビジョニングしても良いです。その場合は以下のように記述します。
persistence: enabled: true ## trueにする storageClassName: "azurefile-csi-standard-retain-loki-jpeast-gzrs" ## 作成したStorageClass名 annotations: {} accessModes: - ReadWriteMany size: 10Gi
-
カスタムのlokiデプロイ
最後に3でカスタムしたファイルを指定してhelmでlokiをデプロイします。
helm upgrade --install loki --namespace={{namespace}} loki/loki -f {{3のファイルのパス}}
これでemptyDirではなく適切なPersistenceVolumeにログを流すことができるようになります。図で表すとこんな感じ。
終わりに
言わずもがなですがログというのはサービスを運用していく上でとっっっっても重要です(エンジニア2年目の当時の私は興味すら持っていなかった...)。アプリのバグ調査や不正アクセスの監視、不正アクセスがあった場合の調査、ユーザーからの問い合わせ対応など様々な場面で役に立ってくれることをここ半年で学びました。インフラに関してはk8sも含めまだまだひよっこな私ですが、今回のアドベントカレンダーでみなさんの記事を見ながら知見を増やせて行けたらなーと思います!
※何か記事内で間違ったことを書いてたらコメントなどでご指摘お願いします...