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:

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:
BlackboxWrapperservice chạy hai processes.- Process đầu chạy
blackbox_exporterbinary. - Process thứ hai listen bucket changes từ GCP và restart process đầu.
- Process đầu chạy
- Service deploy as docker image, cho phép package service alongside
blackbox_exporterbinary.

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_exporterbinary move đến directory accessible từPATH. - Chỉ định entrypoint container chạy
app.rbkhi 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
.startlàm: startblackbox_exporterbinary trong process khác probe endpoints; startsubscribertrong process khác listen bucket changes; gọisleeptrong main process đảm bảo app chạy vô tận.
- Method
BlackboxExporterhoạt động thế nào?- Method
.startdùngexeckernel method chạyblackbox_exporterbinary trong process khác. - Method
.reloadgửi signalSIGHUPlive reloadblackbox_exporterbinary với new configuration. Như từ classProcessNotification, new config file written đến config file location trước khi exporter reload.
- Method
Subscriberhoạt động thế nào?- Method
.listenbắt đầu tạoNotificationConfiguation.NotificationConfigurationlà rule chỉ định ba thứ: topic trong pub/sub nhận notifications; event trigger notifications sent; information contained trong notifications. - Method
#create_notification_configcũng ensure chỉ có mộtNotificationConfiguration; không thì delete everything và tạo một. Đảm bảo notifications sent just once. - Method
.listencũng gọi@subscription.listenstart listening notification changes trong bucket subscribed. Chạy vô tận trong process khác, như giải thích. - Method
#process_notificationgọi cho mọi notification update sent. CóSUPPORTED_FILE_TYPESidentify files trong bucket we care about và ignore rest.
- Method
ProcessNotification: Responsible process notifications, download updated config, write to file, reloadblackbox_exporterbinary.
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.

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.

Tạo PubSub topic trong GCP console.

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ó.

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

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.