Filebeat Lightweight shipper for logs from k8s to Elasticsearch

Beat

สวัสดีครับ DevOps 101 ของเรา วันนี้ จะมาพูดถึง Filebeat นะครับ ..

Filebeat คืออะไร?
Filebeat = Lightweight shipper for logs
“Whether you’re collecting from security devices, cloud, containers, hosts, or OT, Filebeat helps you keep the simple things simple by offering a lightweight way to forward and centralize logs and files.”

ถ้าเอาแบบเข้าใจง่ายๆ ก็คือ ตัวกวาด logs จากที่ต่างๆ เข้ามาที่ centralize logs ในที่นี้ ผมจะพูดถึง k8s cluster (EKS) logs ไปเก็บที่ Elasticsearch ละกันนะครับ ..

จริงๆ ยังมี ตัวอื่นๆ อีก นะครับ ที่ทำงานคล้ายๆ Filebeat เช่น Fluentd, Fluentbit

ในเมื่อเราใช้ ELK Stack ใน Ecosystem ของเราแล้ว การที่เราอยาก search logs ต่างๆ ที่เกิดจาก container ของเรา ได้ง่ายสุด ก็คือการ ship logs จาก k8s cluster ของเรา ไปเก็บไว้บน Elasticsearch แล้วทำการ search ผ่าน Kibana ..

วิธีการ Install Filebeat ใน k8s cluster

ทำได้หลายวิธีครับ ในที่นี้ ผมจะใช้วิธีง่ายๆ ผ่าน kubectl ดังต่อไปนี้

1. Create Secret โดยใส่ค่าที่จำเป็น พวกนี้ลงไป

ELASTICSEARCH_HOST = Endpoint ของ Elasticsearch เรา
ELASTICSEARCH_PORT = Port ที่ Elasticsearch เราทำงานอยู่
ELASTICSEARCH_USERNAME = Username ของ Elasticsearch
ELASTICSEARCH_PASSWORD = Password ของ Elasticsearch
ELASTIC_CLOUD_ID = Cloud ID ในกรณีที่เราใช้ผ่าน Cloud Service ของ Elastic.co
ELASTIC_CLOUD_AUTH = Username:Password ของ Elasticsearch

filebeat-secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: filebeat
  namespace: kube-system
type: Opaque
data:
  ELASTICSEARCH_HOST: aHR0cDovL2xvY2FsaG9zdA==
  ELASTICSEARCH_PASSWORD: 
  ELASTICSEARCH_PORT: OTIwMA==
  ELASTICSEARCH_USERNAME: 
  ELASTIC_CLOUD_AUTH: 
  ELASTIC_CLOUD_ID: 

จากนั้น สั่ง create secret

kubectl create -f filebeat-secret.yaml

2. Create Deployment และ Resource อื่นๆ

filebeat-kubernetes.yaml

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: kube-system
  labels:
    k8s-app: filebeat
data:
  filebeat.yml: |-
    # To enable hints based autodiscover, remove `filebeat.inputs` configuration and uncomment this:
    filebeat.autodiscover:
      providers:
       - type: kubernetes
         node: ${NODE_NAME}
         hints.enabled: true
         hints.default_config:
           type: container
           paths:
             - /var/log/containers/*${data.kubernetes.container.id}.log

    # Filter by container.name
    # filebeat.autodiscover:
    #   providers:
    #     - type: kubernetes
    #       node: ${NODE_NAME}
    #       templates:
    #         - condition:
    #             contains:
    #               kubernetes.container.name: "container01"
    #           config:
    #             - type: container
    #               paths:
    #                 - "/var/log/containers/*-${data.kubernetes.container.id}.log"
    #         - condition:
    #             contains:
    #               kubernetes.container.name: "container02"
    #           config:
    #             - type: container
    #               paths:
    #                 - "/var/log/containers/*-${data.kubernetes.container.id}.log"

    processors:
      - add_cloud_metadata:
      - add_host_metadata:

    cloud.id: ${ELASTIC_CLOUD_ID}
    cloud.auth: ${ELASTIC_CLOUD_AUTH}

    output.elasticsearch:
      hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
      #index: "%{[fields.my_type]}-%{[agent.version]}-%{+yyyy.MM.dd}" 
      username: ${ELASTICSEARCH_USERNAME}
      password: ${ELASTICSEARCH_PASSWORD}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: filebeat
  namespace: kube-system
  labels:
    k8s-app: filebeat
spec:
  selector:
    matchLabels:
      k8s-app: filebeat
  template:
    metadata:
      labels:
        k8s-app: filebeat
    spec:
      serviceAccountName: filebeat
      terminationGracePeriodSeconds: 30
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
        - name: filebeat
          image: docker.elastic.co/beats/filebeat:8.6.2
          args: ["-c", "/etc/filebeat.yml", "-e"]
          env:
            - name: ELASTICSEARCH_HOST
              valueFrom:
                secretKeyRef:
                  name: filebeat
                  key: ELASTICSEARCH_HOST
            - name: ELASTICSEARCH_PORT
              valueFrom:
                secretKeyRef:
                  name: filebeat
                  key: ELASTICSEARCH_PORT
            - name: ELASTICSEARCH_USERNAME
              valueFrom:
                secretKeyRef:
                  name: filebeat
                  key: ELASTICSEARCH_USERNAME
            - name: ELASTICSEARCH_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: filebeat
                  key: ELASTICSEARCH_PASSWORD
            - name: ELASTIC_CLOUD_ID
              valueFrom:
                secretKeyRef:
                  name: filebeat
                  key: ELASTIC_CLOUD_ID
            - name: ELASTIC_CLOUD_AUTH
              valueFrom:
                secretKeyRef:
                  name: filebeat
                  key: ELASTIC_CLOUD_AUTH
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
          securityContext:
            runAsUser: 0
            # If using Red Hat OpenShift uncomment this:
            #privileged: true
          resources:
            limits:
              memory: 200Mi
            requests:
              cpu: 100m
              memory: 100Mi
          volumeMounts:
            - name: config
              mountPath: /etc/filebeat.yml
              readOnly: true
              subPath: filebeat.yml
            - name: data
              mountPath: /usr/share/filebeat/data
            - name: varlibdockercontainers
              mountPath: /var/lib/docker/containers
              readOnly: true
            - name: varlog
              mountPath: /var/log
              readOnly: true
      volumes:
        - name: config
          configMap:
            defaultMode: 0640
            name: filebeat-config
        - name: varlibdockercontainers
          hostPath:
            path: /var/lib/docker/containers
        - name: varlog
          hostPath:
            path: /var/log
        # data folder stores a registry of read status for all files, so we don't send everything again on a Filebeat pod restart
        - name: data
          hostPath:
            # When filebeat runs as non-root user, this directory needs to be writable by group (g+w).
            path: /var/lib/filebeat-data
            type: DirectoryOrCreate
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: filebeat
subjects:
  - kind: ServiceAccount
    name: filebeat
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: filebeat
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: filebeat
  namespace: kube-system
subjects:
  - kind: ServiceAccount
    name: filebeat
    namespace: kube-system
roleRef:
  kind: Role
  name: filebeat
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: filebeat-kubeadm-config
  namespace: kube-system
subjects:
  - kind: ServiceAccount
    name: filebeat
    namespace: kube-system
roleRef:
  kind: Role
  name: filebeat-kubeadm-config
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: filebeat
  labels:
    k8s-app: filebeat
rules:
  - apiGroups: [""] # "" indicates the core API group
    resources:
      - namespaces
      - pods
      - nodes
    verbs:
      - get
      - watch
      - list
  - apiGroups: ["apps"]
    resources:
      - replicasets
    verbs: ["get", "list", "watch"]
  - apiGroups: ["batch"]
    resources:
      - jobs
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: filebeat
  # should be the namespace where filebeat is running
  namespace: kube-system
  labels:
    k8s-app: filebeat
rules:
  - apiGroups:
      - coordination.k8s.io
    resources:
      - leases
    verbs: ["get", "create", "update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: filebeat-kubeadm-config
  namespace: kube-system
  labels:
    k8s-app: filebeat
rules:
  - apiGroups: [""]
    resources:
      - configmaps
    resourceNames:
      - kubeadm-config
    verbs: ["get"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: filebeat
  namespace: kube-system
  labels:
    k8s-app: filebeat
---
 

จากนั้น สั่ง create deployment

kubectl create -f filebeat-kubernetes.yaml

3. Search k8s cluster logs ผ่าน Kibana

ถ้า config ทุกอย่างเราถูกต้อง container ทำงานได้ เราก็จะได้ logs ของ k8s cluster เรา ไปเก็บบน Elasticsearch และทำการ search ผ่าน Kibana ได้เลย

Kibana Search

* เราสามารถ filter input ของ Logs ที่จะ ship ไปเก็บ ที่ Elasticsearch ได้

ตัวอย่างอยู่ใน filebeat-kubernetes.yaml ที่ comment ไว้

เป็นอย่างไรกันบ้างครับ ไม่ยากเลยใช่ไหมครับ สำหรับการ ship logs จาก k8s cluster ของเรา ไปเก็บบน Elasticsearch 🙂

Git Repo: https://github.com/pornpasok/k8s-logs-es-filebeat
Ref: https://www.elastic.co/beats/filebeat

Setup Elasticsearch and Kibana OpenID Connect with Azure AD

elastic.co with Azure AD

สวัสดีครับ DevOps 101 ของเรา วันนี้มีเรื่องมาแนะนำ การ setup Single Sign-On (SSO) ของ App เรา .. ด้วย Azure AD โดยที่เรา ไม่จำเป็นต้องมีสิทธิเป็น admin ของ org นั้นๆ .. เป็น user ธรรมดา ก็สามารถ ทำได้
ในที่นี้ จะใช้ในส่วนของ OpenID Connect (OIDC) นะครับ ซึ่งสามารถ ใช้งานทั้งกับ Google, GitHub, AWS Cognito และอื่นๆ ได้ .. แต่ในที่นี้ผมจะใช้เป็น Azure AD นะครับ

จากความต้องการขององค์กร ที่ต้องการให้ พนักงานทุกคน login ด้วย Azure AD หรือถ้าพูดให้เข้าใจง่าย ก็คือใช้ email บริษัท ในทุกๆ App ที่ใช้งาน เนื่องจากเราใช้ Office365 อยู่แล้ว .. จะได้สะดวกในการจัดการ พนักงาน ที่มีการเข้าออก เปลี่ยนแปลงอยู่เสมอ ไม่ต้อง add แบบ local user ..

ในที่นี้ผมจะยกตัวอย่างการ นำ Azure AD (OpenID Connect) มาใช้กับ ELK Stack โดยสามารถใช้ได้ กับ ELK Stack ที่เรา hosted เอง และใช้ได้กับ elastic.co นะครับ ..

วิธีการ Setup OpenID Connect เราสามารถทำตามขั้นตอน ใน docs ของ elastic.co ได้เลยดังนี้
https://www.elastic.co/guide/en/cloud/current/ec-securing-clusters-oidc-op.html#ec-securing-clusters-oidc-op

จากนั้น เราสามาถทำ Role Mappings ได้ใน Kibana อย่างในที่นี้ ผมจะให้ user ที่ login ผ่าน Azure AD (OIDC) ทั้งหมด มี สิทธิแค่ viewer ก็สามารถทำได้ประมาณนี้

User field: realm.name
Type: text
Value: oidc1 (ชื่อ realm ที่เรา config ในหน้า login ของ Kibana)

Kibana Role Mappings

หรือถ้าเรา ต้องการสร้าง Role mapping เฉพาะเจาะจงว่า user (email) คนไหน สามารถทำอะไรเพิ่มเติมได้ บ้าง เราก็สามารถ สร้างเพิ่มได้ ประมาณนี้

User field: username
Type: text
Value: user@email.com (email ของ user ที่เราต้องการ)

Kibana Role Mappings Specific by Email

จะเห็นว่า เราสามารถนำ Azure AD (OpenID Connect) มาใช้กับ Kibana ของเราได้ ทำให้เราสะดวก ในการจัดการ user และการ ทำ Role Mappings ก็ช่วยให้เรา สามารถกำหนด permission ของแต่ละ user ให้แตกต่างกันได้ .. เราไม่ต้องไป add local user บน Elasticsearch และเราสามารถนำ OpenID Connect ไปใช้กับ App อื่นๆ ของเราได้ด้วยเช่นกันครับ .. ไม่ว่าจะเป็น App ที่เราเขียนเอง หรือ OpenSource ต่างๆ ก็รองรับ OpenID Connect กันเป็นส่วนใหญ่ 🙂

ถ้าเพื่อนๆ สงสัยตรงไหน comment กันเข้ามาถามได้นะครับ ไม่ยากครับ ลองไปทำเล่นดู elastic.co เอง ใช้งานได้ฟรี 14 วันครับ หรือถ้าจะ Hosted เองด้วย docker-compose ง่ายๆ ก็ได้ครับ 🙂

ตัวอย่าง: https://github.com/pornpasok/docker-elk

ออกแบบ DevOps Architecture CI/CD Pipeline ให้เหมาะกับงาน และองค์กร

DevOps Architecture CI/CD Pipeline

สวัสดีครับ .. DevOps 101 ของเรา วันนี้มีโจทย์ มาอันนึงครับ ให้ออกแบบ Architecture, Tech Stack และ CI/CD Pipeline ของ Application ที่มีคนใช้งานจำนวนมาก app นึง ให้กับองค์กร ขนาดใหญ่ ระดับพนักงาน 200,000 คน ++ ..

จริงๆ ในการออกแบบ ไม่มีอะไรถูกอะไรผิดครับ .. หลักการง่ายๆ ก็คือ “Simply the best” .. ทำให้ deploy ง่ายๆ .. รองรับ load สูงๆ ให้ได้ .. มีระบบ Monitoring ที่ดี .. และเหมาะสม กับงานและองค์กร ..

เนื่องจาก ทางองค์กร มี Single Sign-On อยู่แล้ว ซึ่งสามารถใช้งานผ่าน AWS Cognito ได้ .. เราก็เลยสามารถ ใช้งานได้เลย และเอาไปต่อกับ CI/CD Tools ที่จำเป็นของเราได้ ไม่ว่าจะเป็น GitLab, Jenkins, Nexus Repository และอื่นๆ ในอนาคต ..

ในที่นี้ ตัว Application เราเลือกพัฒนาโดยใช้ Next.js เพราะว่า ต้องรองรับ user ที่เข้าใช้งานหลากหลาย ไม่ว่าจะเป็น บน Desktop และ Mobile .. และทางทีม Developer มีความเชี่ยวชาญในส่วนของ Next.js อยู่แล้ว .. ในส่วนของ CI/CD Tools เราเลือกใช้ Jenkins และทำ CI/CD Pipeline เป็น Jenkinsfile (Declarative Pipeline) แบบง่ายๆ stage ตรงไปตรงมา ใครมาอ่าน ก็เข้าใจ .. เพราะว่าอนาคต เราอยากเอา template นี้ ไป reuse ให้ทีมอื่น ในองค์กร เราได้ใช้ตามด้วย ..

ในส่วนของ Artifactory ที่ใช้เก็บ Docker Images และ binary files ต่างๆ เราเลือกใช้ Nexus Repository เป็น private repository ของเรา .. ส่วน SCM (Source Code Management) อันนี้จริงๆ เราคิดว่า เป็นอะไรก็ได้ ไม่ว่าจะ github, gitlab, ecr, bitbucket .. แต่องค์กรเรามี GitLab อยู่แล้ว ก็เลยใช้ตัวนี้ไปเลย .. ส่วนของ static code scan เราเลือกใช้ SonarQube แต่สิ่งที่เราอยากดูจริงๆ ก็เป็นส่วนของ code coverage มากกว่า ..

ในส่วนของ Server ที่เราจะใช้ในการ deploy ตัว Application ของเราขึ้นไป เราเลือกใช้ Amazon EKS (มันคือ k8s cluster บน AWS) เพราะว่าเราไม่ต้องวุ่นวายในการสร้าง k8s cluster ขึ้นมาใช้งานเอง และตัว cost ของ Amazon EKS ก็ไม่ได้แพงกว่า EC2 ธรรมดา เท่าไรนัก ..

ทำไมถึงเลือกใช้ EKS ไม่ใช้ ECS ?

– ผมเองต้องออกตัวไว้ก่อนครับ ว่าความรู้เกี่ยวกับ ECS ของผม แทบจะเป็น 0 .. แต่จากการได้เข้า training กับ AWS ในส่วนของการใช้งาน ECS + DevOps .. ทำให้ผมพอได้รู้บ้างว่า ตัว ECS ทำงานอย่างไร .. เราสามารถ ทำอะไรกับมันได้บ้าง .. ทำให้ผมตัดสินใจได้ทันที ว่างั้นเราไป EKS เถอะ .. เพราะว่า ในการทำ Application ที่รองรับคนใช้งานจำนวนมากนั้น .. การ scale และการ monitoring metrics ต่างๆ ถือว่าเป็นเรื่องสำคัญมาก ซึ่งผมเองคิดว่า EKS ที่เป็น k8s cluster eco system มัน flexible กว่า .. อาจจะเป็นเหตุผลที่เอนเอียงนิดหน่อย จากการที่ผมคุ้นเคยกับ Container Platform ที่เป็น k8s ด้วย .. (k8s, OpenShift)

ในส่วนของ Dashboard & Monitoring Tools ในที่นี้เราเลือกใช้ APM (Application Performance Monitoring) + ELK Stack ในการ monitoring Application ของเรา ที่เป็น Next.js .. ในส่วนของตัว EKS (k8s Cluster) เราเลือกใช้ Lens + Prometheus เป็น manage และ monitoring metrics ต่างๆ .. แต่ตัว Lens เอง มันคือดูแบบ เครื่องใครเครื่องมัน .. อนาคต ถ้าเราอยากทำเป็น dashboard ให้ทั้งทีม ได้ดูผ่านจอ TV LED ร่วมกัน ก็น่าจะเอา Grafana มาทำ dashboard ในส่วนนี้ .. ส่วนตัว Alert Notification เราใช้เป็น webhook ของ MS Teams เพราะว่าทีมเรา และองค์กรเรา ใช้ MS Teams เป็นหลัก ในการทำงานอยู่แล้ว ..

Tech Stack

– Next.js
– GitLab
– Jenkins
– Nexus Repository
– SonarQube
– nginx
– Docker
– Kubernetes (k8s)
– AWS Cognito
– AWS EKS
– Elasticsearch
– Logstash + APM
– Kibana
– Prometheus
– Lens/Grafana
– Microsoft Teams

Jenkins Pipeline Examples

https://github.com/pornpasok/demo-app-k8s

สำหรับท่านใด ที่สนใจ ในการออกแบบ Architecture, Tech Stack และ CI/CD Pipeline ก็สามารถ มา comment พูดคุย แลกเปลี่ยน ปรึกษากันได้ตรงนี้นะครับ .. อย่างที่ผมเน้นย้ำตลอด ไม่มีแบบไหนผิด แบบไหนถูก .. แต่แบบไหนที่เหมาะกับเรา นั่นแหละ คือดีที่สุด ครับ .. 🙂