Vectorとは

現在参画している現場で、ログコレクターとしてVectorを利用しています。
本記事ではVectorの紹介と併せて、Kubernetes環境へデプロイできるようにHelmChart化してみたいと思います。

ログコレクターとは

ログコレクターは複数のソース (ファイル、system など) からログを収集します。
メタデータによるエントリの追加、解析、フィルタリングなどを含む、ログの変換操作を実行できます。
収集したログはSplunkなどのコンポーネントに送信されます。

Vectorとは

OSSのRust製データパイプライン(エージェント)で、可観測性データ(ログ・メトリクス)の収集・変換・送信を行います。
大きく「Sources」「Transforms」「Sinks」の3コンポーネントで構成されています。
様々なデータを収集することが可能で、送信先として指定可能な製品(Splunk、ElasticSerach、Grafana Loki ...)も幅広くサポートされています。 詳細は公式ドキュメントを参照してください。

vector.dev

Vectorの性能

Fluentd等の他エージェントに比べると、高速でメモリ効率が高く、要求の厳しいワークロードの処理に適しているようです。
パフォーマンス比較については、以下の記事が参考になります。

medium.com

HelmChart

ディレクトリ構成は以下となります。

.
|-- Chart.yaml
|-- templates
|   |-- vector-config.yaml
|   |-- vector-daemonset.yaml
|   |-- vector-rbac.yaml
|   |-- vector-service-account.yaml
|   `-- vector-service.yaml
`-- values.yaml
templates/vector-daemonset.yaml

VectorをDaemonSetで定義して、各ノードのコンテナログ(/var/log/pods)を参照できるようにします。
kube-config を参照する必要があるため、SecretにしてVolumeMountしています。

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: "{{ .Release.Name }}-daemonset"
spec:
  selector:
    matchLabels:
      app: "{{ .Release.Name }}-app"
  template:
    metadata:
      labels:
        app: "{{ .Release.Name }}-app"
    spec:
      serviceAccountName: "{{ .Release.Name }}-vector-sa"
      containers:
      - name: "{{ .Release.Name }}-app"
        image: "{{ .Values.vector.image.repository }}:{{ .Values.vector.image.tag }}"
        args: ["--config", "/etc/vector/vector.toml"]
        readinessProbe:
          httpGet:
            path: /health
            port: 8686
          initialDelaySeconds: 60
          periodSeconds: 60
        livenessProbe:
          httpGet:
            path: /health
            port: 8686
          initialDelaySeconds: 60
          periodSeconds: 60
        volumeMounts:
          - name: config-volume
            mountPath: /etc/vector
            readOnly: true
          - name: kube-config
            mountPath: /mnt/vector
            readOnly: true
          - name: pod-logs
            mountPath: /var/log/pods
            readOnly: true
        env:
          - name: TZ
            value: Asia/Tokyo
          - name: VECTOR_SELF_NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: spec.nodeName
          - name: VECTOR_CONFIG_PATH
            value: /etc/vector/vector.toml
        ports:
          - containerPort: 8686
      volumes:
        - name: config-volume
          configMap:
            name: "{{ .Release.Name }}-config"
        - name: kube-config
          secret:
            secretName: kube-config
        - name: pod-logs
          hostPath:
            path: /var/log/pods
      tolerations:
        - key: "node-role.kubernetes.io/master"
          operator: "Exists"
          effect: "NoSchedule"
kube-config

Secret化するkube-config は、以下のような内容となります。
kubectl create secret generic kube-config --from-file=kube-config -n xxxVectorと同じnamespaceにデプロイしてください。

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: xxxx
    server: https://kubernetes.default.svc:443
  name: default
contexts:
- context:
    cluster: default
    user: default
  name: default
current-context: default
kind: Config
preferences: {}
users:
- name: default
  user: xxxx
  client-key-data: xxxx
templates/vector-config.yaml

次にConfigMapです。
Vectorの設定ファイルをConfigMap化しています。

  • sources.kubernetes_logs.kube_config_file にDaemonSetでマウントしたkube-configのパスを指定する必要があります
  • transforms.transform_logs でログの変換やフィルタリングの設定をしています
  • sinks.loki でログの送信先に Grafana Loki のURLを指定しています
apiVersion: v1
kind: ConfigMap
metadata:
  name: "{{ .Release.Name }}-config"
data:
  vector.toml: |-
    # Vector の設定ファイル
    [api]
    enabled = true
    address = "0.0.0.0:8686"

    [sources.kubernetes_logs]
    type = "kubernetes_logs"
    ignore_older_secs = 1800
    max_read_bytes = 10485760
    max_line_bytes = 1048576
    read_from = "end"
    timezone = "Asia/Tokyo"
    kube_config_file = "/mnt/vector/kube-config"
    extra_namespace_label_selector = """
    kubernetes.io/metadata.name!=default,
    kubernetes.io/metadata.name!=kube-system
    """

    [transforms.transform_logs]
    type = "remap"
    inputs = [ "kubernetes_logs" ]
    source = """
    .pod = .kubernetes.pod_name
    .namespace = .kubernetes.pod_namespace
    .container = .kubernetes.container_name
    del(.file)
    del(.kubernetes)
    del(.source_type)
    del(.stream)
    """
    timezone = "Asia/Tokyo"

    [sinks.loki]
    type = "loki"
    inputs = ["transform_logs"]
    compression = "snappy"
    endpoint = "{{ .Values.loki.address }}"
    path = "/loki/api/v1/push"
    out_of_order_action = "drop"
    remove_timestamp = false

    [sinks.loki.encoding]
    codec = "json"
    timestamp_format = "rfc3339"

    [sinks.loki.labels]
    job = "k8s-logs"
    env = "{{ .Values.loki.env }}"

各設定項目の詳細は公式ドキュメントを参照してください。

その他のマニフェストについて特筆すべき点はないため、詳細は割愛します。

# Chart.yaml
apiVersion: v2
name: vector-agent
description: A Helm chart for Vector Agent
version: 1.0.0
# values.yaml
vector:
  image:
    repository: "docker.io/timberio/vector"
    tag: "0.29.1-alpine"
  service:
    type: ClusterIP

loki:
  address: "http://loki-stack.logging.svc:3100"
  env: dev
# templates/vector-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: "{{ .Release.Name }}-service"
spec:
  selector:
    app: "{{ .Release.Name }}-app"
  ports:
  - name: http
    port: 8686
    targetPort: 8686
  type: "{{ .Values.vector.service.type }}"
# templates/vector-service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: "{{ .Release.Name }}-vector-sa"
# templates/vector-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: "{{ .Release.Name }}-vector-clusterrole"
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log", "nodes", "namespaces", "deployments", "statefulsets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["daemonsets"]
    verbs: ["get", "list", "watch"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: "{{ .Release.Name }}-vector-rolebinding"
subjects:
- kind: ServiceAccount
  name: "{{ .Release.Name }}-vector-sa"
  namespace: {{ .Release.Namespace | quote }}
roleRef:
  kind: ClusterRole
  name: "{{ .Release.Name }}-vector-clusterrole"
  apiGroup: rbac.authorization.k8s.io

上記のChartをhelmコマンドでデプロイしてみます。

$ helm version
version.BuildInfo{Version:"v3.10.2", GitCommit:"50f003e5ee8704ec937a756c646870227d7c8b58", GitTreeState:"clean", GoVersion:"go1.18.8"}

# dry run
$ helm install vector-test ./vector-agent-helm-chart/ --dry-run --namespace test-ns --create-namespace
NAME: vector-test
LAST DEPLOYED: Fri May 12 16:08:49 2023
NAMESPACE: test-ns
STATUS: pending-install
REVISION: 1
TEST SUITE: None
HOOKS:
MANIFEST:
---
# Source: vector-agent/templates/vector-service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: "vector-test-vector-sa"
...

$ helm install vector-test ./vector-agent-helm-chart/ --namespace test-ns --create-namespace
NAME: vector-test
LAST DEPLOYED: Fri May 12 16:10:48 2023
NAMESPACE: test-ns
STATUS: deployed
REVISION: 1
TEST SUITE: None

Vectorを使ってみて

2023年5月現在、複数行のログがそれぞれ別レコードとして扱われてしまうので、Grafanaで可視化した際に見づらいと感じました。
コンテナのログをJSONで出力するなどしないと、単一データとして扱われないようです。

github.com

改行の扱いについては、以下の記事も参考になります。

www.datadoghq.com

まとめ

Vectorの紹介とHelmChartにしてデプロイしてみました。
OpenShiftではFluentd が非推奨となり、代わりにVectorが採用されています。
また、Vectorには以下のような特徴があります。

  • ログ・メトリクス収集が可能なエージェント(Fluentdとほぼ同等)
  • 高性能(高速でメモリ効率が高い、CPU消費は多め)
  • Splunk、ElasticSerachなど大体の製品に対応している

参考リンク

Gitレポジトリ。

github.com

公式のHelmChart。

github.com

コンテナイメージはtimberio/vectorという名前で、Dockerhubに公開されています。

hub.docker.com