Lời giới thiệu đầu

Thời gian gần đây, Serverless là một trong những từ khoá hot nhất trong cộng đồng phát triển phần mềm. Serverless là gì? Đúng như cái tên của nó, nghĩa là người lập trình khi phát triển ứng dụng của mình sẽ "không còn cần" đến Server nữa. Thật ra thì "không còn cần" ở đây có nghĩa là "không cần quan tâm", thay vì phải tìm hiểu, thiết kế, setup server, quy trình deploy các kiểu thì giờ đây ta chỉ cần push code lên là xong. Các việc khác đã có dịch vụ Serverless của các nhà cung cấp Cloud lo cho hộ rồi. Hiện tại thì 3 đại gia cloud AWS, Azure, Google Cloud đều cung cấp các dịch vụ Serverless của mình. Bài viết hôm nay sẽ tìm hiểu sơ qua về Cloud Function và tạo một ứng dụng đơn giản với dịch vụ này

Google Cloud Function

Hiện tại thì Google đang cung cấp 3 dịch vụ Serverless là Google Cloud Run, App Engine và Cloud Function. Để hình dùng sự khác biệt giữa 3 dịch vụ này, ta có thể tham khảo hình ảnh sau (được cung cấp chính hãng bởi Google)

uc?id=1d4vwI7kkBT0t3J9b91BNL5RxpAS7ysVM&export=download

Như vậy ta có thể thấy trong 3 lựa chọn, Gcloud Function là lựa chọn đơn giản nhất, thích hợp cho việc xử lý event với đơn vị deploy là Function. Nói một cách đơn giản là bạn viết một function, function đó được deploy và GCloud Function sẽ thực thi function đó giúp bạn.

Hiện tại GCloud Function support 3 runtime để xử lý Function là Go, Node.JS và Python. Việc viết code và deploy thì vô cùng đơn giản, ta có thể tham khảo ví dụ Hello World từ chính Google.

Trước hết, ta cần cài đặt Cloud SDK của Google và khởi tạo 1 project để có thể test GCloud Function. Phần này thì là thủ tục phải có rồi nên mình sẽ không ghi chi tiết ở đây.

Tạo 1 folder helloworld với 1 file helloworld.go với nội dung như sau:

// Package helloworld provides a set of Cloud Functions samples.
package helloworld

import (
        "encoding/json"
        "fmt"
        "html"
        "net/http"
)

// HelloHTTP is an HTTP Cloud Function with a request parameter.
func HelloHTTP(w http.ResponseWriter, r *http.Request) {
        var d struct {
                Name string `json:"name"`
        }
        if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
                fmt.Fprint(w, "Hello, World!")
                return
        }
        if d.Name == "" {
                fmt.Fprint(w, "Hello, World!")
                return
        }
        fmt.Fprintf(w, "Hello, %s!", html.EscapeString(d.Name))
}

Function HelloHTTP có thể được deploy 1 cách vô cùng đơn giản lên GCloud bằng câu lệnh sau:

$ gcloud functions deploy HelloHTTP --runtime go113 --trigger-http --allow-unauthenticated

Ở đây, runtime được chọn cho function là go 1.13 và trigger cho function sẽ là HTTP, tức là function này sẽ xử lý 1 request HTTP gửi đến. Sau khi deploy xong Fucntion sẽ có 1 HTTP endpoint dưới dạng

https://GCP_REGION-PROJECT_ID.cloudfunctions.net/HelloHTTP

Ta có thể test bằng cách gửi request đến endpoint này

curl -X POST https://GCP_REGION-PROJECT_ID.cloudfunctions.net/HelloHTTP -H "Content-Type:application/json"  -d '{"name":"Dep Trai"}'

Sử dụng Google Cloud Function để nhận và gửi thông tin

Nếu các bạn đã sử dụng Google Cloud thì chắc chắn sẽ phải sử dụng Cloud Monitoring, dịch vụ theo dõi tình hình sức khoẻ hệ thông của Google. Dịch vụ này có thể giúp chúng ta theo dõi trạng thái hệ thống, đồng thời tạo thông báo khi một điều kiện nào đó được thoả mãn (như là tổng cpu, memory bị chiếm hữu vượt 1 ngưỡng nhất định). Tuy nhiên hiện tại liên kết kênh thông báo chỉ hỗ trợ 1 số kênh nhất định và không support Webhook của Mattermost (chương trình chat free, siêu việt mà công ty mình tự host để dùng). Do vậy mình quyết tâm tạo một webhook dùng Google Cloud, sau đó dùng webhook này post lên Mattermost

Trước tiên, cứ phải tạo một Incomming Webhook trên Mattermost đã. uc?id=1Jube4Ya-_BAZgH19-uQYp5llhhpEL5la&export=download

Sau đó ta bắt đầu tạo Function của mình. Để tăng thêm chút bảo mật thì ta thêm một param token để check quyền truy cập

// alert_webhook.go
func AlertFunc(w http.ResponseWriter, r *http.Request) {
	token := r.URL.Query().Get("auth_token")
	if token != os.Getenv("AUTH_TOKEN") {
		log.Printf("Unauthorized Access with Token: %s\n", token)
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}
    
    ...
}

Parse request to Cloud Monitoring

Request từ Cloud Monitoring gửi đến webhook sẽ có định dạng như sau

{
		"incident": {
		  "incident_id": "f2e08c333dc64cb09f75eaab355393bz",
		  "resource_id": "i-4a266a2d",
		  "resource_name": "webserver-85",
		  "state": "open",
		  "started_at": 1385085727,
		  "ended_at": null,
		  "policy_name": "Webserver Health",
		  "condition_name": "CPU usage",
		  "url": "https://console.cloud.google.com/monitoring/alerting/incidents?project=PROJECT_ID",
		  "summary": "CPU for webserver-85 is above the threshold of 1% with a value of 28.5%"
		},
        "version": "1.1"
}

Do vậy ta cũng phải định nghĩa struct tương tự để parse data

// alert_webhook.go

type StackDriveIncident struct {
	IncidentID    string `json:"incident_id"`
	ResourceID    string `json:"resource_id"`
	ResourceName  string `json:"resource_name"`
	State         string `json:"state"`
	StartedAt     Time   `json:"started_at"`
	EndedAt       Time   `json:"ended_at"`
	PolicyName    string `json:"policy_name"`
	ConditionName string `json:"condition_name"`
	URL           string `json:"url"`
	Summary       string `json:"summary"`
}

type StackDriveAlert struct {
	Incident StackDriveIncident `json:"incident"`
	Version  string             `json:"version"`
}

Quay lại hàm AlertFunc, việc parse request gửi đến từ Cloud Monitoring cũng rất đơn giản.

// alert_webhook.go
...
func AlertFunc(w http.ResponseWriter, r *http.Request) {	
    ...
    var alert StackDriveAlert
	if err := json.NewDecoder(r.Body).Decode(&alert); err != nil {
		log.Printf("%+v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
    
    ...

Gửi data đến Mattermost

Gửi data đến Webhook của Mattermost cũng vô cùng đơn giản ta chỉ cần gửi một request với data json có định dạng như dưới đây là Mattermost sẻ post message theo account và config đã thiết lập cho webhook này

{ "text": "Test test test" }

Đoạn code để gửi data trong AlertFunc sẽ như sau:

// alert_webhook.go

...
type WebhookRequest struct {
	Text string `json:"text"`
}

func AlertFunc(w http.ResponseWriter, r *http.Request) {	
    ...
	webhookReq := WebhookRequest{
		Text: fmt.Sprintf("##### %s\n%s\n%s\n_%s_", alert.Incident.PolicyName,
			alert.Incident.Summary, alert.Incident.URL,
			alert.Incident.StartedAt.Time().Format(time.RFC1123Z)),
	}

	reqBody, err := json.Marshal(webhookReq)
	if err != nil {
		log.Printf("%+v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	_, err = http.Post(os.Getenv("WEBHOOK_URL"), "application/json", bytes.NewBuffer(reqBody))
	if err != nil {
		log.Printf("%+v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
    w.Write(([]byte("OK")))
 }

Sau đó chỉ cần tạo 1 file .env.yaml để chứa các biến môi trường cần thiết như sau:

AUTH_TOKEN: xxxxxxx
WEBHOOK_URL: https://webhook.url
BASE_URL: https://base.ur

Là ta đã có thể sẵn sàng deploy function của mình lên Google Cloud rồi (yêu cầu đã download SDK, tạo project):

$ gcloud functions deploy AlertFunc --region asia-northeast1 --env-vars-file .env.yaml --runtime go113 --trigger-http --allow-unauthenticated

Vì option trigger là HTTP nên function sẽ được deploy ở địa chỉ như sau

https://{REGION}-{PROJECT_ID}.cloudfunctions.net/AlertFunc

Setup ở Cloud Monitoring và thành quả

Từ màn hình quản lý của Google Cloud truy cập vào phần Monitoring -> Alert -> Edit Notification Channel (https://console.cloud.google.com/monitoring/alerting/notifications), ta có thể tạo một Webhook Notification Channel sử dụng function đã deploy của mình.
uc?id=1RPKcNlMMGtr2t-cjhtpxt_AAEfSfm2UN&export=download

Khi có alert thì Mattermost sẽ push alert dưới dạng text message vào channel đã được thiết lập, ví dụ như sau:
uc?id=10rim8gyjToETYopss8LSovDNJlGK5rT4&export=download

Tổng kết

Rất đơn giản và dễ dàng, ta đã có thể sử dụng Google Cloud Function để tạo một webhook nhận và gửi data. Với các bài toán xử lý event đơn giản và stateless thì Google Cloud Function hoàn toàn là giải pháp hợp lý và có nhiều ưu điểm hơn so với các lựa chọn deploy truyền thống. Với các bài toán phức tạp hơn thì Google Cloud Run và App Engine có lẽ sẽ là các lựa chọn Serverless phù hợp hơn.

Tham khảo