Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Xây Dựng Hệ Thống Giám Sát Uptime Tin Cậy Bằng Ruby Dùng GCE, Cloud Storage, Pub/Sub

Uptime monitoring liên quan đến kiểm tra availability của websites, APIs, và servers. Monitor probe endpoint cho trước trong interval chỉ định để xác định available hay không. Mục tiêu đạt contracted availability level, theo SLA của hệ thống, và xác định difference khi contract không đáp ứng.

Bài này xây dựng uptime monitoring system dựa trên Prometheus blackbox_exporter. Mặc dù build custom HTTP monitoring system có thể trivial, build wrapper quanh exporter cho phép access nhiều probe techniques khác và nhanh chóng monitor các element khác của hệ thống.

Bài cover dùng một số technologies, mô tả từng component trước khi deep dive vào uptime system.

Google Compute Engine (GCE) là gì?

Compute Engine là cloud computing service của Google tương tự EC2 của AWS. GCE secure và customizable đủ fit various workloads từ small machines (lên tới 32 vCPUs, 128 GB memory) đến standard machines (lên tới 224 vCPUs, 896 GB memory) và high-end machines cho intensive workloads. Sử dụng compute on-demand scale theo needs per-time.

GCE hỗ trợ deployment mechanisms khác nhau cho app deployment, bao gồm containers, instance templates, managed instance groups. Cho bài này, bundle Ruby uptime monitor vào docker container để deploy.

Cloud Storage là gì?

Google Cloud Storage là highly available object-storage service tương tự S3 của AWS. Cloud Storage cung cấp nhiều storage features enable several use-cases cho modern apps. Để bắt đầu Cloud Storage trong Ruby, dùng gem google-cloud-storage authenticate, upload/download files:

require 'google/cloud/storage'
def upload_file bucket_name:, file_path:, file_name: nil
  storage = Google::Cloud::Storage.new
  bucket = storage.bucket bucket_name
  file = bucket.create_file file_path, file_name
end

def download_file bucket_name:, file_path:, file_name: nil
  storage = Google::Cloud::Storage.new
  bucket = storage.bucket bucket_name
  file = bucket.file file_name
  file.download file_path
end

Lưu ý: Cần setup GOOGLE_APPLICATION_CREDENTIALS trong environment point đến service account key đúng. Tất cả Google client gems tìm env variable này để authorize; không thì pass auth specific params cho Google::Cloud::Storage.new. Nếu app chạy trong GCE VM, đã setup sẵn trong environment.

Cloud PubSub là gì?

Cloud PubSub là publish/subscribe messaging service do Google Cloud cung cấp. Communication form này facilitate asynchronous service-to-service communication, tương tự SNS của AWS. Xây dựng systems với async communication giúp improve performance, scalability, reliability. Để bắt đầu Cloud PubSub trong Ruby, dùng gem google-cloud-pubsub authenticate, publish, listen events:

require 'google/cloud/pubsub'
def publish_message topic_id:, message: nil
  pubsub = Google::Cloud::Pubsub.new
  topic = pubsub.topic topic_id
  topic.publish_async message do |result|
    raise "Failed to publish message" unless result.succeeded?
    puts "Message published asynchronously"
  end
  topic.async_publisher.stop.wait!
  rescue StandardError => e
  puts "Received error while publishing: #{e.message}"
end

def receive_message subscription_id: nil, wait_time: 200.seconds
  pubsub = Google::Cloud::Pubsub.new
  subscription = pubsub.subscription subscription_id
  subscriber = subscription.listen do |received_message|
    puts "Received message: #{received_message.data}"
    received_message.acknowledge!
  end
  subscriber.start
  sleep wait_time
end

Lưu ý: Authentication mô tả cho Cloud Storage cũng apply ở đây.

Khi leverage Cloud Storage và PubSub, có thể build very interesting solutions. Thường muốn upload object và track updates - life-cycle - create, update, delete, và take specific actions dựa trên certain events. Nếu vẫn abstract, hãy explore hai use-cases:

  • Image Service: Building Image Service. Giả sử muốn tạo giống Cloudinary cung cấp image/video storage, cũng perform transformations. Cloud Storage giúp store và version data, với PubSub, listen events từ bucket và perform pre-processing trên data, thậm chí trước customer request pre-processed version.
  • Distribute Configuration Files. Vấn đề phổ biến infrastructure engineering: roll out configurations đến several servers và easy rollbacks. Giả sử muốn central server responsible server configurations, update config once và distribute config đến fleet servers. Bằng Cloud Storage và Cloud PubSub, build agents trên servers listen qua PubSub get object notifications và take action based trên events. Hơn nữa, nếu bad change (wrong config changes thường见 downtime 😩), perform rollback với object versioning.

Bài này build Ruby wrapper cho Blackbox Exporter dùng use-case thứ hai. Wrapper chạy exporter trong một process và chạy process khác watch config changes từ bucket trong GCP, rồi live reload exporter. Ready? Let's have fun!

Blackbox Exporter là gì?

Blackbox Exporter là open-source tool do Prometheus team build probe endpoints qua HTTP, HTTPS, DNS, TCP, ICMP. Exporter nên deploy alongside Grafana và Prometheus deployment. Complete setup trông như sau:

Xây Dựng Hệ Thống Giám Sát Uptime Tin Cậy Bằng Ruby Dùng GCE, Cloud Storage, Pub/Sub

Blackbox wrapper probe tất cả configured endpoints, Prometheus scrape exporter như target khác. Sau đó, Grafana retrieve data từ Prometheus để graph. Chạy exporter binary như blackbox_exporter --config.file blackbox.yml. Blackbox Exporter cũng cho live reload exporter với new configuration không shutdown binary và restart. Rất useful khi scrape endpoints với intervals tính bằng giây.

BlackboxWrapper Service Specs

Trước deep-dive code, highlight service specs:

  • BlackboxWrapper service chạy hai processes.
    • Process đầu chạy blackbox_exporter binary.
    • Process thứ hai listen bucket changes từ GCP và restart process đầu.
  • Service deploy as docker image, cho phép package service alongside blackbox_exporter binary.

Xây Dựng Hệ Thống Giám Sát Uptime Tin Cậy Bằng Ruby Dùng GCE, Cloud Storage, Pub/Sub

Bắt đầu xây dựng

Đầu tiên, tạo app directory và vào directory.

mkdir blackbox-wrapper && cd blackbox-wrapper

Giống standard Ruby app, dùng bundler manage wrapper dependencies. Tạo Gemfile:

source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
ruby '2.7.2'
gem 'google-cloud-storage'
gem 'google-cloud-pubsub'
gem 'rake'
gem 'pry'

Chạy bundle install.

Tạo file hold code: app.rb.

File này act as entry point service. Vì deploy trong container, file này specified trong CMD command trong Dockerfile sau.

touch app.rb

Tạo Dockerfile

Mặc dù một số items omitted từ file này cố ý. Code dưới highlight critical components cần thiết:

FROM ruby:2.7.2
RUN mkdir /app
WORKDIR /app
COPY . .
# Install other dependencies ...
# Download & Install blackbox exporter
RUN curl -SL \
 https://github.com/prometheus/blackbox_exporter/releases/download/v0.18.0/blackbox_exporter-0.18.0.linux-386.tar.gz | \
 tar xvz -C /tmp && \
 mv /tmp/blackbox_exporter-0.18.0.linux-386/blackbox_exporter /usr/local/bin && \
 mkdir /etc/blackbox && \
 mv /tmp/blackbox_exporter-0.18.0.linux-386/blackbox.yml /etc/blackbox/
# Specify entry point.
CMD ["bundle", "exec", "ruby", "app.rb" ]

Từ trên, lưu ý:

  • Dùng Ruby image - ruby:2.7.2 - base image với Ruby installed.
  • Cài blackbox_exporter binary move đến directory accessible từ PATH.
  • Chỉ định entrypoint container chạy app.rb khi container start up.

Xây Dựng Wrapper Service

Đây là Ruby service glue everything together. Trong main.rb, đặt sau:

require 'rubygems'
require 'bundler/setup'
require "google/cloud/pubsub"
require "google/cloud/storage"
CONFIG_BUCKET = ENV['BUCKET_NAME']
TOPIC = ENV['PUBSUB_TOPIC']
TOPIC_SUBSCRIPTION = ENV['TOPIC_SUBSCRIPTION']

class ProcessNotification
  def initialize(file, attr, blackbox_exporter)
    @file = file
    @attr = attr
    @blackbox_exporter = blackbox_exporter
  end
  def call
    return if @attr['eventType'] == 'OBJECT_DELETE'
    @blackbox_exporter.write @file
    @blackbox_exporter.reload
  end
end

class BlackBoxExporter
  CONFIG_FILE = '/etc/blackbox/blackbox.yml'
  def initialize
    @blackbox_pid = nil
  end
  def start
    return unless @blackbox_pid.nil?
    @blackbox_pid = fork do
      exec('blackbox_exporter', '--config.file', CONFIG_FILE)
    end
  end
  def write(file)
    file.download CONFIG_FILE
  end
  def reload
    # Send SIGHUP signal
    Process.kill('HUP', @blackbox_pid)
  end
  def shutdown
    Process.kill('KILL', @blackbox_pid)
  end
end

class Subscriber
  class NotificationConfigError < StandardError; end
  SUPPORTED_FILE_TYPES = ['blackbox.yml']
  def initialize(blackbox_exporter)
    @pubsub = Google::Cloud::Pubsub.new
    @storage = Google::Cloud::Storage.new
    @subscription_name = ENV['TOPIC_SUBSCRIPTION']
    # Retrieve a subscription
    @bucket = @storage.bucket CONFIG_BUCKET
    @subscription = @pubsub.subscription @subscription_name
    @blackbox_exporter = blackbox_exporter
  end
  def listen
    create_notification_config
    puts "Starting subscriber"
    @subscriber = @subscription.listen do |received_message|
      process_notification(received_message)
    end
    @subscriber.on_error do |exception|
      process_exception(exception)
    end
    @subscriber.start
  end
  def process_notification(received_message)
    data = received_message.message.data
    published_at = received_message.message.published_at
    attributes = received_message.message.attributes
    puts "Data: #{data}, published at #{published_at}, Attr: #{attributes}"
    received_message.acknowledge!
    parsed_data = JSON.parse(data)
    file_name = parsed_data['name']
    return unless SUPPORTED_FILE_TYPES.include?(file_name)
    file = @bucket.file file_name
    process_notification = ProcessNotification.new(file, attributes, @blackbox_exporter)
    process_notification.call
  end
  def process_exception(exception)
    puts "Exception: #{exception.class} #{exception.message}"
  end
  def shutdown
    @subscriber.stop!(10)
  end
  def create_notification_config
    topic = @pubsub.topic TOPIC
    notification_exists = @bucket.notifications.count == 1
    unless notification_exists
      @bucket.notifications.each do |notification|
        notification.delete
      end
    end
    @bucket.create_notification topic.name
    rescue StandardError => e
    raise NotificationConfigError, e.message
  end
end

class BlackboxWrapper
  def initialize
    @blackbox_exporter = BlackBoxExporter.new
    @subscriber = Subscriber.new(@blackbox_exporter)
  end
  def start
    @blackbox_exporter.start
    @subscriber.listen
    at_exit do
      @blackbox_exporter.shutdown
      @subscriber.shutdown
    end
    # Block, letting processing threads continue in the background
    sleep
  end
end

blackbox_wrapper = BlackboxWrapper.new
blackbox_wrapper.start

Mặc dù code nhiều, hãy break down từ bottom:

  • BlackboxWrapper: Entrypoint service.
    • Method .start làm: start blackbox_exporter binary trong process khác probe endpoints; start subscriber trong process khác listen bucket changes; gọi sleep trong main process đảm bảo app chạy vô tận.
  • BlackboxExporter hoạt động thế nào?
    • Method .start dùng exec kernel method chạy blackbox_exporter binary trong process khác.
    • Method .reload gửi signal SIGHUP live reload blackbox_exporter binary với new configuration. Như từ class ProcessNotification, new config file written đến config file location trước khi exporter reload.
  • Subscriber hoạt động thế nào?
    • Method .listen bắt đầu tạo NotificationConfiguation. NotificationConfiguration là rule chỉ định ba thứ: topic trong pub/sub nhận notifications; event trigger notifications sent; information contained trong notifications.
    • Method #create_notification_config cũng ensure chỉ có một NotificationConfiguration; không thì delete everything và tạo một. Đảm bảo notifications sent just once.
    • Method .listen cũng gọi @subscription.listen start listening notification changes trong bucket subscribed. Chạy vô tận trong process khác, như giải thích.
    • Method #process_notification gọi cho mọi notification update sent. Có SUPPORTED_FILE_TYPES identify files trong bucket we care about và ignore rest.
  • ProcessNotification: Responsible process notifications, download updated config, write to file, reload blackbox_exporter binary.

Chạy Service Local

Để chạy service local test, chạy sau ở root app directory:

export BUCKET_NAME='{insert-bucket-name}'
export PUBSUB_TOPIC='{insert-pubsub-topic}'
export TOPIC_SUBSCRIPTION='{insert-subscription-name}'
export GOOGLE_APPLICATION_CREDENTIALS='{insert-path-to-service-key-json}'
bundle exec ruby app.rb

Deploy Service lên Google Compute Engine

Giống nhiều khía cạnh cloud, nhiều cách đạt cùng kết quả, nhưng modern software engineering khuyến khích CI/CD vì vài lý do tốt. Tập trung deploy service từ Github Actions dùng setup-gcloud.

Setup deployment file (.github/workflows/deploy.yml).

name: Build and Deploy to Google Compute Engine
on:
  push:
    branches:
      - main
env:
  PROJECT_ID: ${{ secrets.GCE_PROJECT }}
  GCE_INSTANCE: ${{ secrets.GCE_INSTANCE }}
  GCE_INSTANCE_ZONE: us-central1-a
  BUCKET_NAME: demo-configurations
  PUBSUB_TOPIC: demo-configurations-bucket-notifications
  TOPIC_SUBSCRIPTION: demo-bucket-changes-subscription
jobs:
  setup-build-publish-deploy:
    name: Setup, Build, Publish, and Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      # Setup gcloud CLI
      - uses: google-github-actions/setup-gcloud@master
        with:
          version: '290.0.1'
          service_account_key: ${{ secrets.GCE_SA_KEY }}
          project_id: ${{ secrets.GCE_PROJECT }}
      # Configure Docker to use the gcloud command-line tool as a credential
      # helper for authentication
      - run: |
          gcloud --quiet auth configure-docker
      # Build the Docker image
      - name: Build
        run: |
          docker build --tag "gcr.io/$PROJECT_ID/$GCE_INSTANCE-image:$GITHUB_SHA" .
      # Push the Docker image to Google Container Registry
      - name: Publish
        run: |
          docker push "gcr.io/$PROJECT_ID/$GCE_INSTANCE-image:$GITHUB_SHA"
      - name: Deploy
        run: |
          gcloud compute instances update-container "$GCE_INSTANCE" \
          --zone "$GCE_INSTANCE_ZONE" \
          --container-image "gcr.io/$PROJECT_ID/$GCE_INSTANCE-image:$GITHUB_SHA" \
          --container-env "BUCKET_NAME=$BUCKET_NAME,PUBSUB_TOPIC=$PUBSUB_TOPIC,TOPIC_SUBSCRIPTION=$TOPIC_SUBSCRIPTION"

Lưu ý flag --container-env set trong deploy phase, đảm bảo pass necessary env variables từ Github Actions secrets đến container secure.

Secrets & Environment Variables

Tiếp, setup secrets cho github actions.

Xây Dựng Hệ Thống Giám Sát Uptime Tin Cậy Bằng Ruby Dùng GCE, Cloud Storage, Pub/Sub

Set environment variables cho container với flag --container-env. Vì set từ Github actions, có thể dùng secrets cho sensitive data hoặc env variables cho non-sensitive data.

Tạo GCP Resources

Tạo bucket trong GCP console.

Xây Dựng Hệ Thống Giám Sát Uptime Tin Cậy Bằng Ruby Dùng GCE, Cloud Storage, Pub/Sub

Tạo PubSub topic trong GCP console.

Xây Dựng Hệ Thống Giám Sát Uptime Tin Cậy Bằng Ruby Dùng GCE, Cloud Storage, Pub/Sub

Set service agent của cloud storage bucket - IAM role - pubsub.publisher trong console. Mỗi project có associated Cloud Storage service account responsible background actions, như PubSub notifications. Click tại đây học cách tìm nó.

Xây Dựng Hệ Thống Giám Sát Uptime Tin Cậy Bằng Ruby Dùng GCE, Cloud Storage, Pub/Sub

Cuối cùng, tạo subscription trong GCP Console.

Xây Dựng Hệ Thống Giám Sát Uptime Tin Cậy Bằng Ruby Dùng GCE, Cloud Storage, Pub/Sub

Voila! 🎉 Cloud function đã deploy thành công.

Kết luận

Nếu bạn đọc đến đây, bạn xứng đáng cookie 🍪. Tôi nghĩ đây là version đầu của potentially great solution với multiple optimizations. Ví dụ, có thể đạt:

  • Deploy blackbox_exporter as serverless function support multiple regions, ideal cho uptime monitoring, và deploy master server responsible update bucket config trong Cloud Storage.
  • Potentially, từ điểm trước, abstract thành app integrate vào popular cloud providers đạt same functionality, làm cloud-agnostic. P.S: Popular cloud providers (GCP, AWS, Azure) cung cấp same functionalities across services.
  • Bài tiếp theo, build on this solution provide rollbacks với cloud-storage object versioning, cho phép recover từ updating config với incorrect updates.
  • Deploy với Docker giải quyết packaging problem, nhưng như bạn biết, có various ways package services. Chọn Docker bài này cho simplicity.

Glossary

  • Prometheus là open-source systems monitoring và alerting toolkit. Bao gồm server scrape và store time-series data, client libraries instrument application code, alert manager handle alerts.
  • Grafana là visualization system cho phép query, visualize, alert on, understand metrics, regardless nơi stored.
  • Blackbox Exporter là open-source tool do Prometheus team build probe endpoints qua HTTP, HTTPS, DNS, TCP, ICMP.