Utilizando Log Analytics para monitorar logs de auditoria do Azure RedHat OpenShift
Published Sep 05 2023 09:29 AM 3,031 Views
Microsoft

Utilizando Log Analytics para monitorar logs de auditoria do Azure RedHat OpenShift

 

Introdução

Por padrão, os clusters Azure Red Hat OpenShift possuem uma forma de monitorar os logs de auditoria através do OpenShift Logging, que envolve a instalação do OpenShift Elasticsearch Operator e OpenShift Cluster Logging. Embora essa solução seja eficiente, ela não permite a integração com o Azure Monitor, a solução de monitoramento da Microsoft, nem a centralização dos logs de auditoria de diversos clusters em um único local.

Para demonstrarmos uma solução personalizada, é necessário possuir um cluster Azure Red Hat OpenShift. Caso você não possua um cluster, é possível seguir o tutorial Criando um cluster Azure Red Hat OpenShift e lembre-se de utilizar a opção do pull secret para baixar as imagens da RedHat Pull Secret

 

Pré-requisitos

 

Fluent Bit

Fluent Bit é um sistema de coleta e encaminhamento de registros e logs (logs de eventos e mensagens) desenvolvido como parte do ecossistema Fluentd. É uma solução leve e eficiente projetada para coletar, filtrar e encaminhar logs em ambientes distribuídos.

 

Azure Red Hat OpenShift

Após a criação do cluster, vamos analisar as pastas que estão os logs de auditoria do cluster.

  • Faça o login no cluster, você pode pegar o endereço do cluster no portal do Azure, na aba Overview do cluster criado e clicando no botão Connect238048625-6f564954-2705-49c7-be99-8a3c8035037f.png
  • Clique na URL e utilize o username kubeadmin como user e o password como senha.

 

Instalando o Fluent Bit no cluster

Para fazer a instalação no Azure Red Hat OpenShift, precisamos setar o security context constraints (SCC), para isso você precisa estar logado via cli e ter um usuário com permissão de cluster-admin.

Execute o comando abaixo para criar o SCC:

kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/fluent-bit-openshift-security-context-constraints.yaml

A instalação do Fluent Bit é feita via helm charts, para isso, vamos adicionar o repositório do helm charts do Fluent Bit:

helm repo add fluent https://fluent.github.io/helm-charts

Por padrão a instalação do Fluent Bit os DaemonSets são instalados somente nos workers nodes, mas para ter acesso aos logs de auditoria, precisamos fazer a instalação somente no master node, para isso, vamos criar um arquivo chamado values.yaml com o seguinte conteúdo:

# kind -- DaemonSet or Deployment
kind: DaemonSet

# replicaCount -- Only applicable if kind=Deployment
replicaCount: 1

image:
  repository: cr.fluentbit.io/fluent/fluent-bit
  # Overrides the image tag whose default is {{ .Chart.AppVersion }}
  tag: "latest-debug"
  pullPolicy: Always

testFramework:
  enabled: true
  image:
    repository: busybox
    pullPolicy: Always
    tag: latest

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  create: true
  annotations: {}
  name:

rbac:
  create: true
  nodeAccess: false

# Configure podsecuritypolicy
# Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/
# from Kubernetes 1.25, PSP is deprecated
# See: https://kubernetes.io/blog/2022/08/23/kubernetes-v1-25-release/#pod-security-changes
# We automatically disable PSP if Kubernetes version is 1.25 or higher
podSecurityPolicy:
  create: false
  annotations: {}

openShift:
  # Sets Openshift support
  enabled: true
  # Creates SCC for Fluent-bit when Openshift support is enabled
  securityContextConstraints:
    create: true
    annotations: {}

podSecurityContext: {}
#   fsGroup: 2000

hostNetwork: false
dnsPolicy: ClusterFirst

dnsConfig: {}
#   nameservers:
#     - 1.2.3.4
#   searches:
#     - ns1.svc.cluster-domain.example
#     - my.dns.search.suffix
#   options:
#     - name: ndots
#       value: "2"
#     - name: edns0

hostAliases: []
#   - ip: "1.2.3.4"
#     hostnames:
#     - "foo.local"
#     - "bar.local"

securityContext:
  privileged: true
  runAsUser: 0
  readOnlyRootFilesystem: false
#   capabilities:
#     drop:
#     - ALL
#   readOnlyRootFilesystem: true
#   runAsNonRoot: true
#   runAsUser: 1000

service:
  type: ClusterIP
  port: 2020
  loadBalancerClass:
  loadBalancerSourceRanges: []
  labels: {}
  # nodePort: 30020
  # clusterIP: 172.16.10.1
  annotations: {}
#   prometheus.io/path: "/api/v1/metrics/prometheus"
#   prometheus.io/port: "2020"
#   prometheus.io/scrape: "true"

serviceMonitor:
  enabled: false
#   namespace: monitoring
#   interval: 10s
#   scrapeTimeout: 10s
#   jobLabel: fluentbit
#   selector:
#    prometheus: my-prometheus
#  ## metric relabel configs to apply to samples before ingestion.
#  ##
#  metricRelabelings:
#    - sourceLabels: [__meta_kubernetes_service_label_cluster]
#      targetLabel: cluster
#      regex: (.*)
#      replacement: ${1}
#      action: replace
#  ## relabel configs to apply to samples after ingestion.
#  ##
#  relabelings:
#    - sourceLabels: [__meta_kubernetes_pod_node_name]
#      separator: ;
#      regex: ^(.*)$
#      targetLabel: nodename
#      replacement: $1
#      action: replace
#  scheme: ""
#  tlsConfig: {}

  ## Beare in mind if youn want to collec metrics from a different port
  ## you will need to configure the new ports on the extraPorts property.
  additionalEndpoints: []
  # - port: metrics
  #   path: /metrics
  #   interval: 10s
  #   scrapeTimeout: 10s
  #   scheme: ""
  #   tlsConfig: {}
  #   # metric relabel configs to apply to samples before ingestion.
  #   #
  #   metricRelabelings:
  #     - sourceLabels: [__meta_kubernetes_service_label_cluster]
  #       targetLabel: cluster
  #       regex: (.*)
  #       replacement: ${1}
  #       action: replace
  #   # relabel configs to apply to samples after ingestion.
  #   #
  #   relabelings:
  #     - sourceLabels: [__meta_kubernetes_pod_node_name]
  #       separator: ;
  #       regex: ^(.*)$
  #       targetLabel: nodename
  #       replacement: $1
  #       action: replace

prometheusRule:
  enabled: false
#   namespace: ""
#   additionalLabels: {}
#   rules:
#   - alert: NoOutputBytesProcessed
#     expr: rate(fluentbit_output_proc_bytes_total[5m]) == 0
#     annotations:
#       message: |
#         Fluent Bit instance {{ $labels.instance }}'s output plugin {{ $labels.name }} has not processed any
#         bytes for at least 15 minutes.
#       summary: No Output Bytes Processed
#     for: 15m
#     labels:
#       severity: critical

dashboards:
  enabled: false
  labelKey: grafana_dashboard
  annotations: {}
  namespace: ""

lifecycle: {}
  # preStop:
  #   exec:
  #     command: ["/bin/sh", "-c", "sleep 20"]

livenessProbe:
  httpGet:
    path: /
    port: http

readinessProbe:
  httpGet:
    path: /api/v1/health
    port: http

resources: {}
#   limits:
#     cpu: 100m
#     memory: 128Mi
#   requests:
#     cpu: 100m
#     memory: 128Mi

## only available if kind is Deployment
ingress:
  enabled: false
  className: ""
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts: []
  # - host: fluent-bit.example.tld
  extraHosts: []
  # - host: fluent-bit-extra.example.tld
      ## specify extraPort number
  #   port: 5170
  tls: []
  #  - secretName: fluent-bit-example-tld
  #    hosts:
  #      - fluent-bit.example.tld

## only available if kind is Deployment
autoscaling:
  vpa:
    enabled: false

    annotations: {}

    # List of resources that the vertical pod autoscaler can control. Defaults to cpu and memory
    controlledResources: []

    # Define the max allowed resources for the pod
    maxAllowed: {}
    # cpu: 200m
    # memory: 100Mi
    # Define the min allowed resources for the pod
    minAllowed: {}
    # cpu: 200m
    # memory: 100Mi

    updatePolicy:
      # Specifies whether recommended updates are applied when a Pod is started and whether recommended updates
      # are applied during the life of a Pod. Possible values are "Off", "Initial", "Recreate", and "Auto".
      updateMode: Auto

  enabled: false
  minReplicas: 1
  maxReplicas: 3
  targetCPUUtilizationPercentage: 75
#  targetMemoryUtilizationPercentage: 75
   ## see https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics
  customRules: []
#     - type: Pods
#       pods:
#         metric:
#           name: packets-per-second
#         target:
#           type: AverageValue
#           averageValue: 1k
    ## see https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#support-for-configurable-scaling-behavior
  behavior: {}
#      scaleDown:
#        policies:
#          - type: Pods
#            value: 4
#            periodSeconds: 60
#          - type: Percent
#            value: 10
#            periodSeconds: 60

## only available if kind is Deployment
podDisruptionBudget:
  enabled: false
  annotations: {}
  maxUnavailable: "30%"

nodeSelector:
  node-role.kubernetes.io/master: ''

tolerations: 
  - key: node-role.kubernetes.io/master
    operator: Exists
    effect: NoSchedule

affinity: {}

labels: {}

annotations: {}

podAnnotations: {}

podLabels: {}

## How long (in seconds) a pods needs to be stable before progressing the deployment
##
minReadySeconds:

## How long (in seconds) a pod may take to exit (useful with lifecycle hooks to ensure lb deregistration is done)
##
terminationGracePeriodSeconds:

priorityClassName: ""

env: []
#  - name: FOO
#    value: "bar"

# The envWithTpl array below has the same usage as "env", but is using the tpl function to support templatable string.
# This can be useful when you want to pass dynamic values to the Chart using the helm argument "--set <variable>=<value>"
# https://helm.sh/docs/howto/charts_tips_and_tricks/#using-the-tpl-function
envWithTpl: []
#  - name: FOO_2
#    value: "{{ .Values.foo2 }}"
#
# foo2: bar2

envFrom: []

extraContainers: []
#   - name: do-something
#     image: busybox
#     command: ['do', 'something']

flush: 1

metricsPort: 2020

extraPorts: []
#   - port: 5170
#     containerPort: 5170
#     protocol: TCP
#     name: tcp
#     nodePort: 30517

extraVolumes: []

extraVolumeMounts: []

updateStrategy: {}
#   type: RollingUpdate
#   rollingUpdate:
#     maxUnavailable: 1

# Make use of a pre-defined configmap instead of the one templated here
existingConfigMap: ""

networkPolicy:
  enabled: false
#   ingress:
#     from: []

luaScripts: {}

## https://docs.fluentbit.io/manual/administration/configuring-fluent-bit/classic-mode/configuration-file
config:
  service: |
    [SERVICE]
        Daemon Off
        Flush {{ .Values.flush }}
        Log_Level {{ .Values.logLevel }}
        Parsers_File parsers.conf
        Parsers_File custom_parsers.conf
        HTTP_Server On
        HTTP_Listen 0.0.0.0
        HTTP_Port {{ .Values.metricsPort }}
        Health_Check On

  ## https://docs.fluentbit.io/manual/pipeline/inputs
  inputs: |
    [INPUT]
        Name tail
        Path /var/log/kube-apiserver/*.log
        multiline.parser docker, cri
        Tag audit.kube-apiserver.*
        DB  /tmp/kube_apiserver.db
        Mem_Buf_Limit 50MB
        Refresh_Interval  10 
        Skip_Empty_Lines On
        Buffer_Chunk_Size 5M
        Buffer_Max_Size 50M    
        Skip_Long_Lines Off

    [INPUT]
        Name tail
        Path /var/log/openshift-apiserver/*.log
        multiline.parser docker, cri
        Tag audit.openshift-apiserver.*
        DB  /tmp/openshift-apiserver.db
        Mem_Buf_Limit 50MB
        Refresh_Interval  10 
        Skip_Empty_Lines On
        Buffer_Chunk_Size 5M
        Buffer_Max_Size 50M    
        Skip_Long_Lines Off

    [INPUT]
        Name tail
        Path /var/log/oauth-apiserver/*.log
        multiline.parser docker, cri
        Tag audit.oauth-apiserver.*
        DB  /tmp/oauth-apiserver.db
        Mem_Buf_Limit 50MB
        Refresh_Interval  10 
        Skip_Empty_Lines On
        Buffer_Chunk_Size 5M
        Buffer_Max_Size 50M    
        Skip_Long_Lines Off


  ## https://docs.fluentbit.io/manual/pipeline/filters
  filters: |
    [FILTER]
        Name kubernetes
        Match kube.*
        Merge_Log On
        Keep_Log Off
        K8S-Logging.Parser On
        K8S-Logging.Exclude On

  ## https://docs.fluentbit.io/manual/pipeline/outputs
  outputs: |
    [OUTPUT]
        Name stdout
        Match *

  ## https://docs.fluentbit.io/manual/administration/configuring-fluent-bit/classic-mode/upstream-servers
  ## This configuration is deprecated, please use `extraFiles` instead.
  upstream: {}

  ## https://docs.fluentbit.io/manual/pipeline/parsers
  customParsers: |
    [PARSER]
        Name docker_no_time
        Format json
        Time_Keep Off
        Time_Key time
        Time_Format %Y-%m-%dT%H:%M:%S.%L

  # This allows adding more files with arbitary filenames to /fluent-bit/etc by providing key/value pairs.
  # The key becomes the filename, the value becomes the file content.
  extraFiles: {}
#     upstream.conf: |
#       [UPSTREAM]
#           upstream1
#
#       [NODE]
#           name       node-1
#           host       127.0.0.1
#           port       43000
#     example.conf: |
#       [OUTPUT]
#           Name example
#           Match foo.*
#           Host bar

# The config volume is mounted by default, either to the existingConfigMap value, or the default of "fluent-bit.fullname"
volumeMounts:
  - name: config
    mountPath: /fluent-bit/etc/fluent-bit.conf
    subPath: fluent-bit.conf
  - name: config
    mountPath: /fluent-bit/etc/custom_parsers.conf
    subPath: custom_parsers.conf

daemonSetVolumes:
  - name: varlog
    hostPath:
      path: /var/log
  - name: varlibdockercontainers
    hostPath:
      path: /var/lib/docker/containers
  - name: etcmachineid
    hostPath:
      path: /etc/machine-id
      type: File

daemonSetVolumeMounts:
  - name: varlog
    mountPath: /var/log
  - name: varlibdockercontainers
    mountPath: /var/lib/docker/containers
    readOnly: true
  - name: etcmachineid
    mountPath: /etc/machine-id
    readOnly: true

args: []

command: []

# This supports either a structured array or a templatable string
initContainers: []

# Array mode
# initContainers:
#   - name: do-something
#     image: bitnami/kubectl:1.22
#     command: ['kubectl', 'version']

# String mode
# initContainers: |-
#   - name: do-something
#     image: bitnami/kubectl:{{ .Capabilities.KubeVersion.Major }}.{{ .Capabilities.KubeVersion.Minor }}
#     command: ['kubectl', 'version']

logLevel: info

Se desejar comparar o arquivo que está sendo criado com o arquivo oficial do Fluent Bit, você pode acessar o repositório do Fluent Bit, o arquivo yaml acima também tem a configuração para as pastas abaixo de logs do Azure Red Hat OpenShift que usam a tag [INPUT].

  • /var/log/kube-apiserver
  • /var/log/openshift-apiserver
  • /var/log/oauth-apiserver

Nessa configuração acima estão também estamos usando a imagem com a tag “latest-debug”, com essa tag é possível ver os logs do Fluent Bit no console do pod após a instalação do Fluent Bit no cluster, para isso basta executar o comando abaixo:

ls /var/log/kube-apiserver
ls /var/log/openshift-apiserver
ls /var/log/oauth-apiserver

Para realizar a instalação, esteja na mesma pasta em que o arquivo values.yaml foi criado e execute o comando abaixo.

kubectl create namespace logging
helm install fluent-bit fluent/fluent-bit --namespace logging --values values.yaml                                                                                                 

Logo após instalado, vá ao dashboard do seu cluster, selecione workloads e pods na aba lateral e selecione o project como logging, você deve ter a mesma quantidade de pods que o cluster tem de worker nodes, no meu caso são três workers nodes.

1.png

Com a configuração atual estamos somente lendo os arquivos de logs e mostrando no terminal.

9.png

 

Criando um Log Analytics workspace

Para enviarmos os logs para o Azure Monitor precisamos criar um Log Analytics workspace, para isso acesse siga os passos

Após a criação do Log Analytics workspace e acesse o mesmo e na menu lateral nos settings clique no Agents.

2.png

Salve o Workspace ID e o Primary Key, pois vamos usar os mesmo para a nova configuração.

Agora precisamos adicionar mais um output na configuração do ConfigMap do Fluent Bit.

  • Vá no ConfigMap(fluent-bit) e adicione o output abaixo no final do arquivo e clique no salvar.

      ## https://docs.fluentbit.io/manual/pipeline/outputs
    
        [OUTPUT]
            Name            azure
            Match           *
            Customer_ID     ${WorkspaceId}
            Shared_Key      ${SharedKey}
            Log_Type        AuditOpenshift
  • Execute o comando abaixo para criar uma Secret com o WorkspaceId e SharedKey (que é o seu Primary Key). Mude o xxxx para o seus valores

    kubectl create secret generic fluentbit-secret --from-literal=SharedKey="xxxx" --from-literal=WorkspaceId="xxxx" -n logging
  • Após criar a secret você pode verificar a mesma rodando o comando abaixo.

    kubectl get secret fluentbit-secret  -n logging
  • Agora precisamos adicionar secret no DaemonSet, para isso vá no menu lateral e selecione DaemonSets e clique no fluent-bit e  selecione Enviroments

  • 0.png

  • Clique no Add from ConfigMap or Secret3.png

  • Adicione as environments SharedKey e WorkspaceId e no Select a resource , selecione o Secret que foi criado anteriormente fluent-bit-secret, deixe igual a imagem abaixo e clique no save.4.png

  • Para que a nova configuração seja aplicada, é necessário excluir os Pods atuais; execute o comando abaixo.

    kubectl delete pods -l app.kubernetes.io/instance=fluent-bit  -n logging
  • Após deletar os pods, você pode verificar que os novos pods já estão sendo criados com a nova configuração, para isso execute o comando abaixo.

    kubectl get pods -l app.kubernetes.io/instance=fluent-bit  -n logging
    # Utilize o nome do primeiro de pod que aparecer e execute o comando abaixo para ver os logs do pod.
    kubectl logs fluent-bit-xxxx -n logging  | grep "customer_id="
  • Vai mostrar os logs como abaixo, mostrando que o output para o Log Analytics workspace a foi enviado com sucesso.

      [2023/06/06 16:37:07] [ info] [output:azure:azure.1] customer_id=247446f4-e70c-4338-87d3-ba4f902a82c9, HTTP status=200
      [2023/06/06 16:37:07] [ info] [output:azure:azure.1] customer_id=247446f4-e70c-4338-87d3-ba4f902a82c9, HTTP status=200
      [2023/06/06 16:37:08] [ info] [output:azure:azure.1] customer_id=247446f4-e70c-4338-87d3-ba4f902a82c9, HTTP status=200
      [2023/06/06 16:37:08] [ info] [output:azure:azure.1] customer_id=247446f4-e70c-4338-87d3-ba4f902a82c9, HTTP status=200
      [2023/06/06 16:37:09] [ info] [output:azure:azure.1] customer_id=247446f4-e70c-4338-87d3-ba4f902a82c9, HTTP status=200

 

Vizualizando os logs de auditoria no Log Analytics workspace

  1. Entre no portal da azure, busque na barra de pesquisa do Log Analytics workspace e na lista selecione o Log Analytics workspace que foi criado nos passos anteriores.

  2. No menu lateral selecione logs como na imagem abaixo.

  3. Vai abrir uma tela de queries e feche a mesma.

  4. Em tables, abra custom logs e deve ter uma tabela com no nome AuditOpenshift_CL

  5. Vá no campo e coloque o comando abaixo e clique no Run

    AuditOpenshift_CL |
    take 100 
  6. Após rodar o comando, irá mostrar todos os logs de auditoria que estão sendo enviados para o Log Analytics workspace6.png

 

Conclusão

 

Em resumo, o Fluent Bit é uma ferramenta poderosa para coletar e enviar logs para o Log Analytics Workspace da Azure. Com a configuração correta, você pode coletar logs de vários serviços e aplicativos em execução em seu cluster Kubernetes(OpenShift) e enviá-los para o Log Analytics Workspace para análise e monitoramento. Além disso, o Fluent Bit é altamente configurável e pode ser personalizado para atender às suas necessidades específicas. Esperamos que este guia tenha sido útil para você começar a usar o Fluent Bit em seu ambiente Kubernetes(OpenShift).

 

Referências

 

Co-Authors
Version history
Last update:
‎Sep 05 2023 09:29 AM
Updated by: