iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0

在我們搜尋 5G 核心網路的時候,時常會看到以下關鍵字:

  • NFV - Network Functions Virtualization
  • SDN - Software Defined Networking
  • Cloud Native

其實這些技術不僅是應用在核心網路,只要是任何大型的網路服務系統,那麼它們的 system design 也都跟這些技術脫不了關係。

雲原生的系統監控

當我們將 Network Function 虛擬化跳脫專用硬體的限制後,我們可以讓核心網路運作在筆電、桌機、工作站,或是電腦中的 Container、公有雲、私有雲等等。
讓核心網路運作在公有雲或是私有雲平台其實也就實踐了 Cloud Native 的設計概念:

雲端原生架構和技術是設計、建構和操作工作負載的方法,這些工作負載是在雲端中建置,並充分利用雲端運算模型。

對於一個 Cloud Native 應用來說,如何搜集運作在不同節點上的系統資訊、錯誤偵測與告警系統的建置就會是一個挑戰。幸運的是,CNCF(Cloud Native Computing Foundation)啟動了孵化器的計畫,在 CNCF 孵化器下也產出了很多知名的專案,如:Argo、Helm、Prometheus... 等等,本章的重點就會圍繞在 Prometheus 進行介紹。

Prometheus

image

只要點開 prometheus 的官網就可以看到一句大大的標語:

Power your metrics and alerting with the leading
open-source monitoring solution.

這段標語清楚的告訴我們 prometheus 是一個開源的 monitoring solution。
此外,prometheus 不止作為一個監控系統,它還是一個時序性的資料庫:

  • 它能夠儲存多個 dimension 的資料
  • 支援 long-term storage
  • 強大的查詢功能(使用 PromQL 查詢特定的資料)
  • 使用 pulling 的方式向 application 蒐集資訊
  • 提供多種圖形和儀表板
  • 超容易設定

metrics

  • Counter:累加的資料,重設值為 0,常用於 HTTP request 錯誤的出現次數或是 error exception 出現次數。
  • Gauge:與 Counter 不同,Gauge 類型的 metric 是可以增減的,可以用來記錄像是 CPU、Memory 的使用量。
  • Histogram:主要使用在表示一段時間範圍內的資料蒐集,以長條圖呈現。
  • Summary:表示一段時間內的資料蒐集的總結。

Exporter & PromQL

Exporter 用於匯出應用程式採集的 metrics,我們會將這些 exporter 新增到 prometheus 的設定檔案,讓 prometheus 定期的向這些 exporter 取得資料。
常見的 exporter 有:

  • 用於蒐集作業系統和硬體相關資料的 Node exporter
  • 搜集 Container 資訊的 cadvisor

此外,我們也可以通過 prometheus 提供的函式庫在應用程式中客製化自己的 exporter,以 Network Function 為例,我們可以在它啟動時使用一個 goroutine 進行資料的採集(Collector),並且利用函式庫實作(Exporter)。這樣一來,我們就能夠讓 prometheus 採集到來自 Network Function 提供的訊息。

而 PromQL 則是 prometheus 提供給使用者查詢資料庫內容的語法,我們可以利用 http request 呼叫 prometheus 的 web api 得到我們想要查詢的資料。
有了這些資料以後,我們可以自己撰寫後台應用,或是使用成熟的開源專案將 prometheus 採集到的資訊圖表化。

整合 Prometheus 與 free5GC

完整程式碼可以參考:

本節將示範如何在現有的 free5GC 專案上將 Prometheus 整合進去,為了方便環境的模擬,這是的實驗一樣使用了 docker compose 進行。
在這次的實驗中,我希望在 AMF 內部計算 init registration 的成功率,為了達到目的,我們需要考慮以下事項:

  • 為 AMF 實作 Prometheus exporter
  • 安裝與設定 Prometheus

實作 Prometheus exporter

1. 實作 monitor package:

+++ b/internal/monitor/monitor.go
@@ -0,0 +1,23 @@
+package monitor
+
+import "github.com/free5gc/amf/internal/context"
+
+func GetAmountOfSuccessRegistration() float64 {
+	return context.AMF_Self().RegisSuccess
+}
+
+func GetAmountOfReceivedRegistration() float64 {
+	return context.AMF_Self().RegisTry
+}
+
+func IncAmountOfSuccessRegistration() {
+	context.AMF_Self().Lock.Lock()
+	context.AMF_Self().RegisSuccess++
+	context.AMF_Self().Lock.Unlock()
+}
+
+func IncAmountOfReceivedRegistration() {
+	context.AMF_Self().Lock.Lock()
+	context.AMF_Self().RegisTry++
+	context.AMF_Self().Lock.Unlock()
+}
+++ b/internal/monitor/exporter.go
@@ -0,0 +1,38 @@
+package monitor
+
+import (
+	"net/http"
+
+	"github.com/free5gc/amf/internal/logger"
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
+)
+
+type regisCollector struct {
+	regisDetail *prometheus.Desc
+}
+
+func NewRegistrationCollector() *regisCollector {
+	return &regisCollector{
+		regisDetail: prometheus.NewDesc("registration_metric",
+			"Amount of successful registration & received regis req",
+			nil, nil),
+	}
+}
+
+func (collector *regisCollector) Describe(ch chan<- *prometheus.Desc) {
+	ch <- collector.regisDetail
+}
+
+func (collector *regisCollector) Collect(ch chan<- prometheus.Metric) {
+	regisSuccess := GetAmountOfSuccessRegistration()
+	regisTry := GetAmountOfReceivedRegistration()
+	ch <- prometheus.MustNewConstMetric(collector.regisDetail, prometheus.CounterValue, regisSuccess/regisTry)
+}
+
+func (collector *regisCollector) Start() {
+	prometheus.MustRegister(collector)
+	http.Handle("/metrics", promhttp.Handler())
+	logger.HttpLog.Infoln("Monitor service is listening on 3000 port")
+	logger.HttpLog.Errorln(http.ListenAndServe(":3000", nil))
+}

這邊簡單講解一下程式碼:

  • prometheus.NewDesc() 用於新增一個 metric descriptor,如果這個 metric 有 label,也需要在新增的時候帶上。
  • prometheus.MustRegister(collector) 用於註冊 metric。
  • Collect() 用於搜集 metric,所以我們可以看到這邊將 prometheus.MustNewConstMetric() 建立的資訊傳進 ch 當中。
  • http.Handle("/metrics", promhttp.Handler()) 用來註冊 prometheu 的處理函式。

2. 修改 init registration handler:

+++ b/internal/gmm/handler.go
@@ -17,6 +17,7 @@ import (
 
 	"github.com/free5gc/amf/internal/context"
 	gmm_message "github.com/free5gc/amf/internal/gmm/message"
+	"github.com/free5gc/amf/internal/monitor"
 	ngap_message "github.com/free5gc/amf/internal/ngap/message"
 	"github.com/free5gc/amf/internal/sbi/consumer"
 	"github.com/free5gc/amf/internal/sbi/producer/callback"
@@ -571,6 +572,7 @@ func IdentityVerification(ue *context.AmfUe) bool {
 
 func HandleInitialRegistration(ue *context.AmfUe, anType models.AccessType) error {
 	ue.GmmLog.Infoln("Handle InitialRegistration")
+	monitor.IncAmountOfReceivedRegistration()
 
 	amfSelf := context.AMF_Self()
 
@@ -715,6 +717,7 @@ func HandleInitialRegistration(ue *context.AmfUe, anType models.AccessType) erro
 
 	if anType == models.AccessType__3_GPP_ACCESS {
 		gmm_message.SendRegistrationAccept(ue, anType, nil, nil, nil, nil, nil)
+		monitor.IncAmountOfSuccessRegistration()
 	} else {
 		// TS 23.502 4.12.2.2 10a ~ 13: if non-3gpp, AMF should send initial context setup request to N3IWF first,
 		// and send registration accept after receiving initial context setup response

3. 啟動 monitor service:

+++ b/cmd/main.go
@@ -10,6 +10,7 @@ import (
 	"github.com/urfave/cli"
 
 	"github.com/free5gc/amf/internal/logger"
+	"github.com/free5gc/amf/internal/monitor"
 	"github.com/free5gc/amf/internal/util"
 	"github.com/free5gc/amf/pkg/service"
 	"github.com/free5gc/util/version"
@@ -60,6 +61,8 @@ func action(c *cli.Context) error {
 	logger.AppLog.Infoln(c.App.Name)
 	logger.AppLog.Infoln("AMF version: ", version.GetVersion())
 
+	regisMetric := monitor.NewRegistrationCollector()
+	go regisMetric.Start()
 	AMF.Start()

安裝與設定 Prometheus

1. 於專案內新增 prometheus.yaml

+global:
+   scrape_interval: 5s
+   external_labels:
+       monitor: 'demo-monitor'
+scrape_configs:
+  - job_name: 'prometheus'
+    static_configs:
+      - targets: ['localhost:9090']
+  - job_name: 'api_monitor'
+    scrape_interval: 5s
+    static_configs:
+      - targets: ['prom.free5gc.org']

2. 在 docker-compose.yaml 內新增 prometheus 服務:

+  prometheus:
+    image: prom/prometheus:v2.1.0
+    volumes:
+       - ./prometheus.yaml:/etc/prometheus/prometheus.yaml
+    command:
+     - '--config.file=/etc/prometheus/prometheus.yaml'
+    ports:
+     - '9090:9090'
+    networks:
+      privnet:
+        aliases:
+        - prom.free5gc.org

讓 amf 的 3000 port 對應到本機的 3000 port:

     command: ./amf -c ./config/amfcfg.yaml
     expose:
       - "8000"
+    ports:
+     - '3000:3000'
     volumes:
       - ./config/amfcfg.yaml:/free5gc/config/amfcfg.yaml

3. 修改 base 的 docker image:

 RUN cd $GOPATH/src \
     && git clone --recursive -b v3.2.1 -j `nproc` https://github.com/free5gc/free5gc.git
 
+# prometheus branch of AMF
+RUN cd $GOPATH/src/free5gc/NFs/amf \
+    && git remote add ian https://github.com/ianchen0119/amf.git \
+    && git fetch ian \
+    && git checkout ian/feat/prom

這個步驟的目的是將 amf 的 branch 切換到剛剛我們修改過的版本上。

結果

首先必須確定我們已經將 subscriber data 新增到核網端:
image

接著,一樣打開 ueransim container 的終端機進行註冊:

$ docker exec -it <container_id> bash
$ ./nr-ue -c config/uecfg.yaml

當修改過的 free5gc-compose 成功啟動後,我們可以使用瀏覽器訪問 Prometheus 提供的頁面:

http://127.0.0.1:9090/graph

並且找到我們註冊的 registration_metric:
image

訪問 http://127.0.0.1:3000/metrics 同樣可以看到 AMF collector 回傳的 metrics 資訊。

總結

本篇文章帶大家在 AMF 上實作一個簡易的 prometheus collector,並且利用先前介紹過的 free5gc-compose 專案將整個核心網路與 prometheus 運作在 container 上,如果筆者有興趣還可以繼續深入實作更多資訊的 monitor 並且結合 Grafana 實作一個核心網路的監控系統!

References


上一篇
使用 Docker compose 模擬網路拓墣:free5gc-compose 專案解說
下一篇
Kubernetes 淺淺談
系列文
5G 核心網路與雲原生開發之亂彈阿翔36
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言