Repository: dragonflyoss/Dragonfly2
Pull request #816 (2021-11-23 04:05:10)

docs: metrics configuration
Signed-off-by: Gaius <gaius.qi@gmail.com>

<!--- Provide a general summary of your changes in the Title above -->

## Description
- Add metrics configuration document.
<!--- Describe your changes in detail -->

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## Screenshots (if appropriate):

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [x] Documentation Update (if none of the other choices apply)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] Code compiles correctly.
- [x] My code follows the code style of this project.
- [x] My change requires a change to the documentation.
- [x] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.



Patch:
diff --git a/cdn/cdn.go b/cdn/cdn.go
index 21588205..a2e78a04 100644
--- a/cdn/cdn.go
+++ b/cdn/cdn.go
@@ -40,6 +40,7 @@ import (
 	"d7y.io/dragonfly/v2/pkg/rpc"
 	"d7y.io/dragonfly/v2/pkg/rpc/manager"
 	managerclient "d7y.io/dragonfly/v2/pkg/rpc/manager/client"
+	"d7y.io/dragonfly/v2/pkg/util/hostutils"
 	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 )
 
@@ -126,7 +127,7 @@ func New(cfg *config.Config) (*Server, error) {
 		// Register to manager
 		if _, err := s.managerClient.UpdateCDN(&manager.UpdateCDNRequest{
 			SourceType:   manager.SourceType_CDN_SOURCE,
-			HostName:     iputils.HostName,
+			HostName:     hostutils.FQDNHostname,
 			Ip:           s.config.AdvertiseIP,
 			Port:         int32(s.config.ListenPort),
 			DownloadPort: int32(s.config.DownloadPort),
@@ -167,7 +168,7 @@ func (s *Server) Serve() error {
 		go func() {
 			logger.Info("start keepalive to manager")
 			s.managerClient.KeepAlive(s.config.Manager.KeepAlive.Interval, &manager.KeepAliveRequest{
-				HostName:   iputils.HostName,
+				HostName:   hostutils.FQDNHostname,
 				SourceType: manager.SourceType_CDN_SOURCE,
 				ClusterId:  uint64(s.config.Manager.CDNClusterID),
 			})
@@ -175,7 +176,7 @@ func (s *Server) Serve() error {
 	}
 
 	// Generate GRPC listener
-	var listen = iputils.HostIP
+	var listen = iputils.IPv4
 	if s.config.AdvertiseIP != "" {
 		listen = s.config.AdvertiseIP
 	}
diff --git a/cdn/config/config.go b/cdn/config/config.go
index 812e8371..1ec7582a 100644
--- a/cdn/config/config.go
+++ b/cdn/config/config.go
@@ -131,7 +131,7 @@ func NewDefaultBaseProperties() *BaseProperties {
 		GCMetaInterval:          DefaultGCMetaInterval,
 		TaskExpireTime:          DefaultTaskExpireTime,
 		StorageMode:             DefaultStorageMode,
-		AdvertiseIP:             iputils.HostIP,
+		AdvertiseIP:             iputils.IPv4,
 		Manager: ManagerConfig{
 			KeepAlive: KeepAliveConfig{
 				Interval: DefaultKeepAliveInterval,
diff --git a/cdn/rpcserver/rpcserver.go b/cdn/rpcserver/rpcserver.go
index 28a026a0..bffe25eb 100644
--- a/cdn/rpcserver/rpcserver.go
+++ b/cdn/rpcserver/rpcserver.go
@@ -38,7 +38,7 @@ import (
 	"d7y.io/dragonfly/v2/pkg/rpc/cdnsystem"
 	cdnserver "d7y.io/dragonfly/v2/pkg/rpc/cdnsystem/server"
 	"d7y.io/dragonfly/v2/pkg/util/digestutils"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
+	"d7y.io/dragonfly/v2/pkg/util/hostutils"
 	"d7y.io/dragonfly/v2/pkg/util/net/urlutils"
 	"d7y.io/dragonfly/v2/pkg/util/stringutils"
 )
@@ -140,7 +140,7 @@ func (css *server) ObtainSeeds(ctx context.Context, req *cdnsystem.SeedRequest,
 	for piece := range pieceChan {
 		psc <- &cdnsystem.PieceSeed{
 			PeerId:   peerID,
-			HostUuid: idgen.CDN(iputils.HostName, int32(css.cfg.ListenPort)),
+			HostUuid: idgen.CDNHostID(hostutils.FQDNHostname, int32(css.cfg.ListenPort)),
 			PieceInfo: &base.PieceInfo{
 				PieceNum:    piece.PieceNum,
 				RangeStart:  piece.PieceRange.StartIndex,
@@ -165,7 +165,7 @@ func (css *server) ObtainSeeds(ctx context.Context, req *cdnsystem.SeedRequest,
 	}
 	psc <- &cdnsystem.PieceSeed{
 		PeerId:          peerID,
-		HostUuid:        idgen.CDN(iputils.HostName, int32(css.cfg.ListenPort)),
+		HostUuid:        idgen.CDNHostID(hostutils.FQDNHostname, int32(css.cfg.ListenPort)),
 		Done:            true,
 		ContentLength:   task.SourceFileLength,
 		TotalPieceCount: task.PieceTotal,
diff --git a/client/config/peerhost.go b/client/config/peerhost.go
index 8d3c599d..813957b9 100644
--- a/client/config/peerhost.go
+++ b/client/config/peerhost.go
@@ -95,7 +95,7 @@ func (p *DaemonOption) Convert() error {
 	// AdvertiseIP
 	ip := net.ParseIP(p.Host.AdvertiseIP)
 	if ip == nil || net.IPv4zero.Equal(ip) {
-		p.Host.AdvertiseIP = iputils.HostIP
+		p.Host.AdvertiseIP = iputils.IPv4
 	} else {
 		p.Host.AdvertiseIP = ip.String()
 	}
diff --git a/client/config/peerhost_darwin.go b/client/config/peerhost_darwin.go
index 58029cb2..f6f357b5 100644
--- a/client/config/peerhost_darwin.go
+++ b/client/config/peerhost_darwin.go
@@ -27,6 +27,7 @@ import (
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/pkg/basic"
 	"d7y.io/dragonfly/v2/pkg/basic/dfnet"
+	"d7y.io/dragonfly/v2/pkg/util/hostutils"
 	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 )
 
@@ -57,9 +58,9 @@ var peerHostConfig = DaemonOption{
 		ScheduleTimeout: clientutil.Duration{Duration: DefaultScheduleTimeout},
 	},
 	Host: HostOption{
-		Hostname:       iputils.HostName,
+		Hostname:       hostutils.Hostname,
 		ListenIP:       net.IPv4zero.String(),
-		AdvertiseIP:    iputils.HostIP,
+		AdvertiseIP:    iputils.IPv4,
 		SecurityDomain: "",
 		Location:       "",
 		IDC:            "",
diff --git a/client/config/peerhost_linux.go b/client/config/peerhost_linux.go
index be861e21..d735dc7b 100644
--- a/client/config/peerhost_linux.go
+++ b/client/config/peerhost_linux.go
@@ -27,6 +27,7 @@ import (
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/pkg/basic"
 	"d7y.io/dragonfly/v2/pkg/basic/dfnet"
+	"d7y.io/dragonfly/v2/pkg/util/hostutils"
 	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 )
 
@@ -57,9 +58,9 @@ var peerHostConfig = DaemonOption{
 		ScheduleTimeout: clientutil.Duration{Duration: DefaultScheduleTimeout},
 	},
 	Host: HostOption{
-		Hostname:       iputils.HostName,
+		Hostname:       hostutils.Hostname,
 		ListenIP:       "0.0.0.0",
-		AdvertiseIP:    iputils.HostIP,
+		AdvertiseIP:    iputils.IPv4,
 		SecurityDomain: "",
 		Location:       "",
 		IDC:            "",
diff --git a/cmd/dependency/dependency.go b/cmd/dependency/dependency.go
index 00dee5aa..abeea2c2 100644
--- a/cmd/dependency/dependency.go
+++ b/cmd/dependency/dependency.go
@@ -51,6 +51,7 @@ import (
 	"d7y.io/dragonfly/v2/internal/dfpath"
 	"d7y.io/dragonfly/v2/pkg/basic/dfnet"
 	"d7y.io/dragonfly/v2/pkg/unit"
+	"d7y.io/dragonfly/v2/pkg/util/hostutils"
 	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 	"d7y.io/dragonfly/v2/version"
 )
@@ -84,6 +85,7 @@ func InitCobra(cmd *cobra.Command, useConfigFile bool, config interface{}) {
 		// Add common cmds only on root cmd
 		cmd.AddCommand(VersionCmd)
 		cmd.AddCommand(newDocCommand(cmd.Name()))
+		cmd.AddCommand(PluginCmd)
 	}
 }
 
@@ -103,7 +105,7 @@ func InitMonitor(verbose bool, pprofPort int, otelOption base.TelemetryOption) f
 				pprofPort, _ = freeport.GetFreePort()
 			}
 
-			debugAddr := fmt.Sprintf("%s:%d", iputils.HostIP, pprofPort)
+			debugAddr := fmt.Sprintf("%s:%d", iputils.IPv4, pprofPort)
 			viewer.SetConfiguration(viewer.WithAddr(debugAddr))
 
 			logger.With("pprof", fmt.Sprintf("http://%s/debug/pprof", debugAddr),
@@ -242,7 +244,7 @@ func initJaegerTracer(otelOption base.TelemetryOption) (func(), error) {
 		// Record information about this application in an Resource.
 		sdktrace.WithResource(resource.NewWithAttributes(
 			semconv.ServiceNameKey.String(otelOption.ServiceName),
-			semconv.ServiceInstanceIDKey.String(fmt.Sprintf("%s|%s", iputils.HostName, iputils.HostIP)),
+			semconv.ServiceInstanceIDKey.String(fmt.Sprintf("%s|%s", hostutils.FQDNHostname, iputils.IPv4)),
 			semconv.ServiceNamespaceKey.String("dragonfly"),
 			semconv.ServiceVersionKey.String(version.GitVersion))),
 	)
diff --git a/cmd/dependency/plugin_cmd.go b/cmd/dependency/plugin_cmd.go
new file mode 100644
index 00000000..11668903
--- /dev/null
+++ b/cmd/dependency/plugin_cmd.go
@@ -0,0 +1,86 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dependency
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"github.com/spf13/cobra"
+
+	"d7y.io/dragonfly/v2/internal/dfpath"
+	"d7y.io/dragonfly/v2/internal/dfplugin"
+)
+
+var PluginCmd = &cobra.Command{
+	Use:               "plugin",
+	Short:             "show plugin",
+	Long:              `show the plugin details of dragonfly.`,
+	Args:              cobra.NoArgs,
+	DisableAutoGenTag: true,
+	SilenceUsage:      true,
+	Run: func(cmd *cobra.Command, args []string) {
+		ListAvailablePlugins()
+	},
+}
+
+func ListAvailablePlugins() {
+	fmt.Fprintf(os.Stderr, "search plugin in %s\n", dfpath.PluginsDir)
+	files, err := ioutil.ReadDir(dfpath.PluginsDir)
+	if os.IsNotExist(err) {
+		fmt.Fprintf(os.Stderr, "no plugin found\n")
+		return
+	}
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "read plugin dir %s error: %s\n", dfpath.PluginsDir, err)
+		return
+	}
+	for _, file := range files {
+		var attr []byte
+		fileName := file.Name()
+		if file.IsDir() {
+			fmt.Fprintf(os.Stderr, "not support directory: %s\n", fileName)
+			continue
+		}
+		subs := dfplugin.PluginFormatExpr.FindStringSubmatch(fileName)
+		if len(subs) != 3 {
+			fmt.Fprintf(os.Stderr, "not valid plugin name: %s\n", fileName)
+			continue
+		}
+		typ, name := subs[1], subs[2]
+		switch typ {
+		case string(dfplugin.PluginTypeResource), string(dfplugin.PluginTypeScheduler), string(dfplugin.PluginTypeManager):
+			_, data, err := dfplugin.Load(dfplugin.PluginType(typ), name, map[string]string{})
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "not valid plugin binary format %s: %q\n", fileName, err)
+				continue
+			}
+			attr, err = json.Marshal(data)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "marshal attribute for %s error: %q\n", fileName, err)
+				continue
+			}
+		default:
+			fmt.Fprintf(os.Stderr, "not support plugin type: %s\n", typ)
+			continue
+		}
+
+		fmt.Printf("%s plugin %s, location: %s, attribute: %s\n", typ, name, fileName, string(attr))
+	}
+}
diff --git a/cmd/dfget/cmd/root.go b/cmd/dfget/cmd/root.go
index f06c741f..2976aa9d 100644
--- a/cmd/dfget/cmd/root.go
+++ b/cmd/dfget/cmd/root.go
@@ -84,7 +84,7 @@ var rootCmd = &cobra.Command{
 
 		fmt.Printf("--%s--  %s\n", start.Format("2006-01-02 15:04:05"), dfgetConfig.URL)
 		fmt.Printf("dfget version: %s\n", version.GitVersion)
-		fmt.Printf("current user: %s, default peer ip: %s\n", basic.Username, iputils.HostIP)
+		fmt.Printf("current user: %s, default peer ip: %s\n", basic.Username, iputils.IPv4)
 		fmt.Printf("output path: %s\n", dfgetConfig.Output)
 
 		//  do get file
diff --git a/docs/en/deployment/configuration/cdn.yaml b/docs/en/deployment/configuration/cdn.yaml
index 114a39bd..4cd2f8aa 100644
--- a/docs/en/deployment/configuration/cdn.yaml
+++ b/docs/en/deployment/configuration/cdn.yaml
@@ -55,6 +55,11 @@ base:
     location:
     idc:
 
+  # enable prometheus metrics
+  # metrics:
+  #  # metrics service address
+  #  addr: ":8000"
+
 plugins:
   storagedriver:
     - name: disk
diff --git a/docs/en/deployment/configuration/manager.yaml b/docs/en/deployment/configuration/manager.yaml
index 136fac28..7faf208a 100644
--- a/docs/en/deployment/configuration/manager.yaml
+++ b/docs/en/deployment/configuration/manager.yaml
@@ -34,6 +34,7 @@ database:
     host: dragonfly
     port: 6379
     db: 0
+
 # manager server cache
 # cache:
 #   # redis cache configure
@@ -46,3 +47,8 @@ database:
 #     size: 10000
 #     # cache ttl configure
 #     ttl: 30s
+
+# enable prometheus metrics
+# metrics:
+#  # metrics service address
+#  addr: ":8000"
diff --git a/docs/en/deployment/configuration/scheduler.yaml b/docs/en/deployment/configuration/scheduler.yaml
index e20b0917..6657c208 100644
--- a/docs/en/deployment/configuration/scheduler.yaml
+++ b/docs/en/deployment/configuration/scheduler.yaml
@@ -97,11 +97,11 @@ job:
     # backendDB
     backendDB: ""
 
-# Whether to disable CDN
+# whether to disable CDN
 # default: false
 disableCDN: false
 
-# Console shows log on console
+# console shows log on console
 # default: false
 console: false
 
@@ -120,3 +120,8 @@ jaeger: ""
 # service name used in tracer
 # default: dragonfly-scheduler
 service-name: dragonfly-scheduler
+
+# enable prometheus metrics
+# metrics:
+#  # metrics service address
+#  addr: ":8000"
diff --git a/docs/zh-CN/deployment/configuration/cdn.yaml b/docs/zh-CN/deployment/configuration/cdn.yaml
index 859b5aa1..91827d89 100644
--- a/docs/zh-CN/deployment/configuration/cdn.yaml
+++ b/docs/zh-CN/deployment/configuration/cdn.yaml
@@ -62,6 +62,11 @@ base:
     # IDC(Internet Data Center)，互联网数据中心
     idc: ""
 
+  # 开启数据收集服务
+  # metrics:
+  #  # 数据服务地址
+  #  addr: ":8000"
+
 plugins:
   storagedriver:
     - name: disk
diff --git a/docs/zh-CN/deployment/configuration/manager.yaml b/docs/zh-CN/deployment/configuration/manager.yaml
index 96afa9ca..60930e48 100644
--- a/docs/zh-CN/deployment/configuration/manager.yaml
+++ b/docs/zh-CN/deployment/configuration/manager.yaml
@@ -32,6 +32,7 @@ database:
     host: dragonfly
     port: 6379
     db: 0
+
 # 缓存配置
 # cache:
 #   # redis 缓存配置
@@ -44,3 +45,8 @@ database:
 #     size: 10000
 #     # ttl 配置
 #     ttl: 30s
+
+# 开启数据收集服务
+# metrics:
+#  # 数据服务地址
+#  addr: ":8000"
diff --git a/docs/zh-CN/deployment/configuration/scheduler.yaml b/docs/zh-CN/deployment/configuration/scheduler.yaml
index 19bfe631..1d6108ad 100644
--- a/docs/zh-CN/deployment/configuration/scheduler.yaml
+++ b/docs/zh-CN/deployment/configuration/scheduler.yaml
@@ -123,3 +123,8 @@ jaeger: ""
 # tracer 中使用的 service-name
 # 默认值：dragonfly-cdn
 service-name: dragonfly-scheduler
+
+# 开启数据收集服务
+# metrics:
+#  # 数据服务地址
+#  addr: ":8000"
diff --git a/go.mod b/go.mod
index bf5003af..c3a1fc2c 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.15
 
 require (
 	github.com/RichardKnop/machinery v1.10.6
+	github.com/Showmax/go-fqdn v1.0.0
 	github.com/VividCortex/mysqlerr v1.0.0
 	github.com/agiledragon/gomonkey v2.0.2+incompatible
 	github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible
diff --git a/go.sum b/go.sum
index 7cb2c69a..d2d082e4 100644
--- a/go.sum
+++ b/go.sum
@@ -61,6 +61,8 @@ github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae/go.mod h1:rJJ8
 github.com/RichardKnop/machinery v1.10.6 h1:wviOkVLVM9DaNFAOtXEuZsr9d+Okm4VSw7AILVLIhyc=
 github.com/RichardKnop/machinery v1.10.6/go.mod h1:qT0dXDPzsGqwHoYWO12Gb25MxA/9HfxaqdIaZp9ofWM=
 github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
+github.com/Showmax/go-fqdn v1.0.0 h1:0rG5IbmVliNT5O19Mfuvna9LL7zlHyRfsSvBPZmF9tM=
+github.com/Showmax/go-fqdn v1.0.0/go.mod h1:SfrFBzmDCtCGrnHhoDjuvFnKsWjEQX/Q9ARZvOrJAko=
 github.com/VividCortex/mysqlerr v1.0.0 h1:5pZ2TZA+YnzPgzBfiUWGqWmKDVNBdrkf9g+DNe1Tiq8=
 github.com/VividCortex/mysqlerr v1.0.0/go.mod h1:xERx8E4tBhLvpjzdUyQiSfUxeMcATEQrflDAfXsqcAE=
 github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw=
diff --git a/internal/dfplugin/dfplugin.go b/internal/dfplugin/dfplugin.go
index d95847fd..28d2cc29 100644
--- a/internal/dfplugin/dfplugin.go
+++ b/internal/dfplugin/dfplugin.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"path"
 	"plugin"
+	"regexp"
 
 	"d7y.io/dragonfly/v2/internal/dfpath"
 )
@@ -39,6 +40,8 @@ const (
 	PluginMetaKeyName = "name"
 )
 
+var PluginFormatExpr = regexp.MustCompile("d7y-(resource|manager|scheduler)-plugin-([a-z0-9]+).so")
+
 type PluginType string
 
 const (
diff --git a/internal/idgen/host_id.go b/internal/idgen/host_id.go
new file mode 100644
index 00000000..894200aa
--- /dev/null
+++ b/internal/idgen/host_id.go
@@ -0,0 +1,29 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package idgen
+
+import (
+	"fmt"
+)
+
+func HostID(hostname string, port int32) string {
+	return fmt.Sprintf("%s-%d", hostname, port)
+}
+
+func CDNHostID(hostname string, port int32) string {
+	return fmt.Sprintf("%s_CDN", HostID(hostname, port))
+}
diff --git a/internal/idgen/uuid.go b/internal/idgen/uuid.go
index ddc6427e..7684705e 100644
--- a/internal/idgen/uuid.go
+++ b/internal/idgen/uuid.go
@@ -17,15 +17,9 @@
 package idgen
 
 import (
-	"fmt"
-
 	"github.com/google/uuid"
 )
 
 func UUIDString() string {
 	return uuid.New().String()
 }
-
-func CDN(cdnHostname string, serverPort int32) string {
-	return fmt.Sprintf("%s-%d_CDN", cdnHostname, serverPort)
-}
diff --git a/pkg/rpc/cdnsystem/server/server.go b/pkg/rpc/cdnsystem/server/server.go
index 0d335400..4d2dfdc7 100644
--- a/pkg/rpc/cdnsystem/server/server.go
+++ b/pkg/rpc/cdnsystem/server/server.go
@@ -32,6 +32,7 @@ import (
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/rpc/cdnsystem"
 	"d7y.io/dragonfly/v2/pkg/safe"
+	"d7y.io/dragonfly/v2/pkg/util/hostutils"
 	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 )
 
@@ -136,8 +137,8 @@ func StatSeedStart(taskID, url string) {
 	logger.StatSeedLogger.Info("Start Seed",
 		zap.String("TaskID", taskID),
 		zap.String("URL", url),
-		zap.String("SeederIp", iputils.HostIP),
-		zap.String("SeederHostName", iputils.HostName))
+		zap.String("SeederIP", iputils.IPv4),
+		zap.String("SeederHostname", hostutils.FQDNHostname))
 }
 
 func StatSeedFinish(taskID, url string, success bool, err error, startAt, finishAt time.Time, traffic, contentLength int64) {
@@ -147,8 +148,8 @@ func StatSeedFinish(taskID, url string, success bool, err error, startAt, finish
 		zap.Bool("Success", success),
 		zap.String("TaskID", taskID),
 		zap.String("URL", url),
-		zap.String("SeederIp", iputils.HostIP),
-		zap.String("SeederHostName", iputils.HostName),
+		zap.String("SeederIP", iputils.IPv4),
+		zap.String("SeederHostname", hostutils.FQDNHostname),
 		zap.Time("StartAt", startAt),
 		zap.Time("FinishAt", finishAt),
 		zap.Int64("Traffic", traffic),
diff --git a/pkg/rpc/manager/manager.pb.go b/pkg/rpc/manager/manager.pb.go
index ed84a383..a65c7f01 100644
--- a/pkg/rpc/manager/manager.pb.go
+++ b/pkg/rpc/manager/manager.pb.go
@@ -22,12 +22,13 @@
 package manager
 
 import (
+	reflect "reflect"
+	sync "sync"
+
 	_ "github.com/envoyproxy/protoc-gen-validate/validate"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	emptypb "google.golang.org/protobuf/types/known/emptypb"
-	reflect "reflect"
-	sync "sync"
 )
 
 const (
diff --git a/pkg/rpc/scheduler/server/server.go b/pkg/rpc/scheduler/server/server.go
index 91e26c08..3b695916 100644
--- a/pkg/rpc/scheduler/server/server.go
+++ b/pkg/rpc/scheduler/server/server.go
@@ -26,6 +26,7 @@ import (
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/pkg/rpc"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
+	"d7y.io/dragonfly/v2/pkg/util/hostutils"
 	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 	"d7y.io/dragonfly/v2/scheduler/metrics"
 )
@@ -67,8 +68,8 @@ func (p *proxy) RegisterPeerTask(ctx context.Context, req *scheduler.PeerTaskReq
 		zap.Bool("Success", isSuccess),
 		zap.String("URL", req.Url),
 		zap.String("TaskID", resp.TaskId),
-		zap.String("SchedulerIP", iputils.HostIP),
-		zap.String("SchedulerHostName", iputils.HostName),
+		zap.String("SchedulerIP", iputils.IPv4),
+		zap.String("SchedulerHostname", hostutils.FQDNHostname),
 		zap.Any("Peer", req.PeerHost),
 	)
 
@@ -99,8 +100,8 @@ func (p *proxy) ReportPeerResult(ctx context.Context, req *scheduler.PeerResult)
 		zap.String("PeerIP", req.SrcIp),
 		zap.String("SecurityDomain", req.SecurityDomain),
 		zap.String("IDC", req.Idc),
-		zap.String("SchedulerIP", iputils.HostIP),
-		zap.String("SchedulerHostName", iputils.HostName),
+		zap.String("SchedulerIP", iputils.IPv4),
+		zap.String("SchedulerHostname", hostutils.FQDNHostname),
 		zap.Int64("ContentLength", req.ContentLength),
 		zap.Int64("Traffic", req.Traffic),
 		zap.Uint32("Cost", req.Cost),
diff --git a/pkg/util/hostutils/host_utils.go b/pkg/util/hostutils/host_utils.go
new file mode 100644
index 00000000..0dcced1f
--- /dev/null
+++ b/pkg/util/hostutils/host_utils.go
@@ -0,0 +1,51 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package hostutils
+
+import (
+	"os"
+
+	"github.com/Showmax/go-fqdn"
+)
+
+var Hostname string
+var FQDNHostname string
+
+func init() {
+	Hostname = hostname()
+	FQDNHostname = fqdnHostname()
+}
+
+// Get kernel hostname
+func hostname() string {
+	name, err := os.Hostname()
+	if err != nil {
+		panic(err)
+	}
+
+	return name
+}
+
+// Get FQDN hostname
+func fqdnHostname() string {
+	fqdn, err := fqdn.FqdnHostname()
+	if err != nil {
+		panic(err)
+	}
+
+	return fqdn
+}
diff --git a/pkg/util/net/iputils/ip_utils.go b/pkg/util/net/iputils/ip_utils.go
index 3041d8b0..b2c8d69b 100644
--- a/pkg/util/net/iputils/ip_utils.go
+++ b/pkg/util/net/iputils/ip_utils.go
@@ -24,23 +24,15 @@ import (
 	"github.com/pkg/errors"
 )
 
-var HostIP string
-var HostName string
+var IPv4 string
 
 func init() {
 	ip, err := externalIPv4()
-	if ip == "" {
+	if err != nil {
 		panic(err)
-	} else {
-		HostIP = ip
 	}
 
-	name, err := os.Hostname()
-	if name == "" {
-		panic(err)
-	} else {
-		HostName = name
-	}
+	IPv4 = ip
 }
 
 // IsIPv4 returns whether the ip is a valid IPv4 Address.
@@ -75,7 +67,6 @@ func externalIPv4() (string, error) {
 	}
 
 	if len(values) > 0 {
-		sort.Strings(values)
 		return values[0], nil
 	}
 
@@ -83,9 +74,10 @@ func externalIPv4() (string, error) {
 }
 
 // ipAddrs returns all the valid IPs available.
+// refer to https://github.com/dragonflyoss/Dragonfly2/pull/652
 func ipAddrs() ([]net.IP, error) {
 	// use prefer ip possible
-	var prefer []net.IP
+	var prefers []net.IP
 	host, err := os.Hostname()
 	if err == nil {
 		addrs, _ := net.LookupIP(host)
@@ -93,7 +85,7 @@ func ipAddrs() ([]net.IP, error) {
 			if addr == nil || addr.IsLoopback() || addr.IsLinkLocalUnicast() {
 				continue
 			}
-			prefer = append(prefer, addr)
+			prefers = append(prefers, addr)
 		}
 	}
 
@@ -102,7 +94,8 @@ func ipAddrs() ([]net.IP, error) {
 		return nil, err
 	}
 
-	var ipAddrs []net.IP
+	var preferAddrs []net.IP
+	var normalAddrs []net.IP
 	for _, iface := range ifaces {
 		if iface.Flags&net.FlagUp == 0 {
 			continue // interface down
@@ -128,18 +121,25 @@ func ipAddrs() ([]net.IP, error) {
 				continue
 			}
 			var isPrefer bool
-			for _, a := range prefer {
-				if a.Equal(ip) {
+			for _, preferAddr := range prefers {
+				if preferAddr.Equal(ip) {
 					isPrefer = true
 				}
 			}
 			if isPrefer {
-				ipAddrs = append([]net.IP{ip}, ipAddrs...)
+				preferAddrs = append(preferAddrs, ip)
 			} else {
-				ipAddrs = append(ipAddrs, ip)
+				normalAddrs = append(normalAddrs, ip)
 			}
 		}
 	}
+	sortIP(preferAddrs)
+	sortIP(normalAddrs)
+	return append(preferAddrs, normalAddrs...), nil
+}
 
-	return ipAddrs, nil
+func sortIP(addrs []net.IP) {
+	sort.Slice(addrs, func(i, j int) bool {
+		return addrs[i].String() < addrs[j].String()
+	})
 }
diff --git a/scheduler/config/config.go b/scheduler/config/config.go
index 0ffdc7af..f82c8040 100644
--- a/scheduler/config/config.go
+++ b/scheduler/config/config.go
@@ -25,6 +25,7 @@ import (
 
 	"d7y.io/dragonfly/v2/cmd/dependency/base"
 	dc "d7y.io/dragonfly/v2/internal/dynconfig"
+	"d7y.io/dragonfly/v2/pkg/util/hostutils"
 	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 )
 
@@ -62,8 +63,8 @@ func New() *Config {
 			},
 		},
 		Server: &ServerConfig{
-			IP:   iputils.HostIP,
-			Host: iputils.HostName,
+			IP:   iputils.IPv4,
+			Host: hostutils.FQDNHostname,
 			Port: 8002,
 		},
 		DynConfig: &DynConfig{
diff --git a/scheduler/config/dynconfig.go b/scheduler/config/dynconfig.go
index 3ad3af81..97b80e2f 100644
--- a/scheduler/config/dynconfig.go
+++ b/scheduler/config/dynconfig.go
@@ -29,7 +29,6 @@ import (
 	"d7y.io/dragonfly/v2/manager/types"
 	"d7y.io/dragonfly/v2/pkg/rpc/manager"
 	managerclient "d7y.io/dragonfly/v2/pkg/rpc/manager/client"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 )
 
 var (
@@ -322,21 +321,21 @@ func (d *dynconfig) Stop() error {
 
 type managerClient struct {
 	managerclient.Client
-	SchedulerClusterID uint
+	config *Config
 }
 
-func NewManagerClient(client managerclient.Client, schedulerClusterID uint) dc.ManagerClient {
+func NewManagerClient(client managerclient.Client, cfg *Config) dc.ManagerClient {
 	return &managerClient{
-		Client:             client,
-		SchedulerClusterID: schedulerClusterID,
+		Client: client,
+		config: cfg,
 	}
 }
 
 func (mc *managerClient) Get() (interface{}, error) {
 	scheduler, err := mc.GetScheduler(&manager.GetSchedulerRequest{
-		HostName:           iputils.HostName,
+		HostName:           mc.config.Server.Host,
 		SourceType:         manager.SourceType_SCHEDULER_SOURCE,
-		SchedulerClusterId: uint64(mc.SchedulerClusterID),
+		SchedulerClusterId: uint64(mc.config.Manager.SchedulerClusterID),
 	})
 	if err != nil {
 		return nil, err
diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go
index bdaf22b7..453bdcdd 100644
--- a/scheduler/scheduler.go
+++ b/scheduler/scheduler.go
@@ -31,7 +31,6 @@ import (
 	"d7y.io/dragonfly/v2/pkg/rpc"
 	"d7y.io/dragonfly/v2/pkg/rpc/manager"
 	managerclient "d7y.io/dragonfly/v2/pkg/rpc/manager/client"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 	"d7y.io/dragonfly/v2/scheduler/config"
 	"d7y.io/dragonfly/v2/scheduler/core"
 	"d7y.io/dragonfly/v2/scheduler/job"
@@ -98,7 +97,7 @@ func New(cfg *config.Config) (*Server, error) {
 	options := []dynconfig.Option{dynconfig.WithLocalConfigPath(dependency.GetConfigPath("scheduler"))}
 	if s.managerClient != nil && cfg.DynConfig.Type == dynconfig.ManagerSourceType {
 		options = append(options,
-			dynconfig.WithManagerClient(config.NewManagerClient(s.managerClient, cfg.Manager.SchedulerClusterID)),
+			dynconfig.WithManagerClient(config.NewManagerClient(s.managerClient, cfg)),
 			dynconfig.WithExpireTime(cfg.DynConfig.ExpireTime),
 		)
 	}
@@ -140,7 +139,7 @@ func New(cfg *config.Config) (*Server, error) {
 
 	// Initialize job service
 	if cfg.Job.Redis.Host != "" {
-		s.job, err = job.New(context.Background(), cfg.Job, cfg.Manager.SchedulerClusterID, iputils.HostName, s.service)
+		s.job, err = job.New(context.Background(), cfg.Job, cfg.Manager.SchedulerClusterID, cfg.Server.Host, s.service)
 		if err != nil {
 			return nil, err
 		}
diff --git a/scheduler/supervisor/cdn.go b/scheduler/supervisor/cdn.go
index 5fe350a0..ab8d35f6 100644
--- a/scheduler/supervisor/cdn.go
+++ b/scheduler/supervisor/cdn.go
@@ -317,7 +317,7 @@ func cdnsToHosts(cdns []*config.CDN) map[string]*Host {
 			}
 		}
 
-		id := idgen.CDN(cdn.HostName, cdn.Port)
+		id := idgen.CDNHostID(cdn.HostName, cdn.Port)
 		hosts[id] = NewCDNHost(id, cdn.IP, cdn.HostName, cdn.Port, cdn.DownloadPort, cdn.SecurityGroup, cdn.Location, cdn.IDC, options...)
 	}
 


Test patch:
diff --git a/internal/idgen/host_id_test.go b/internal/idgen/host_id_test.go
new file mode 100644
index 00000000..baa8c404
--- /dev/null
+++ b/internal/idgen/host_id_test.go
@@ -0,0 +1,109 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package idgen
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestHostID(t *testing.T) {
+	tests := []struct {
+		name     string
+		hostname string
+		port     int32
+		expect   func(t *testing.T, d string)
+	}{
+		{
+			name:     "generate HostID",
+			hostname: "foo",
+			port:     8000,
+			expect: func(t *testing.T, d string) {
+				assert := assert.New(t)
+				assert.Len(d, 8)
+			},
+		},
+		{
+			name:     "generate HostID with empty host",
+			hostname: "",
+			port:     8000,
+			expect: func(t *testing.T, d string) {
+				assert := assert.New(t)
+				assert.Len(d, 5)
+			},
+		},
+		{
+			name:     "generate HostID with zero port",
+			hostname: "foo",
+			port:     0,
+			expect: func(t *testing.T, d string) {
+				assert := assert.New(t)
+				assert.Len(d, 5)
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			tc.expect(t, HostID(tc.hostname, tc.port))
+		})
+	}
+}
+
+func TestCDNHostID(t *testing.T) {
+	tests := []struct {
+		name     string
+		hostname string
+		port     int32
+		expect   func(t *testing.T, d string)
+	}{
+		{
+			name:     "generate CDNHostID with ipv4",
+			hostname: "foo",
+			port:     8000,
+			expect: func(t *testing.T, d string) {
+				assert := assert.New(t)
+				assert.Len(d, 12)
+			},
+		},
+		{
+			name:     "generate CDNHostID with empty string",
+			hostname: "",
+			port:     8000,
+			expect: func(t *testing.T, d string) {
+				assert := assert.New(t)
+				assert.Len(d, 9)
+			},
+		},
+		{
+			name:     "generate CDNHostID with zero port",
+			hostname: "foo",
+			port:     0,
+			expect: func(t *testing.T, d string) {
+				assert := assert.New(t)
+				assert.Len(d, 9)
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			tc.expect(t, CDNHostID(tc.hostname, tc.port))
+		})
+	}
+}
diff --git a/internal/idgen/peer_id_test.go b/internal/idgen/peer_id_test.go
index 6d6d7002..6ba937b3 100644
--- a/internal/idgen/peer_id_test.go
+++ b/internal/idgen/peer_id_test.go
@@ -69,7 +69,7 @@ func TestCDNPeerID(t *testing.T) {
 		expect func(t *testing.T, d interface{})
 	}{
 		{
-			name: "generate id with ipv4",
+			name: "generate CDNPeerID with ipv4",
 			ip:   "127.0.0.1",
 			expect: func(t *testing.T, d interface{}) {
 				assert := assert.New(t)
@@ -77,7 +77,7 @@ func TestCDNPeerID(t *testing.T) {
 			},
 		},
 		{
-			name: "generate id with ipv6",
+			name: "generate CDNPeerID with ipv6",
 			ip:   "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b",
 			expect: func(t *testing.T, d interface{}) {
 				assert := assert.New(t)
@@ -85,7 +85,7 @@ func TestCDNPeerID(t *testing.T) {
 			},
 		},
 		{
-			name: "generate id with empty string",
+			name: "generate CDNPeerID with empty string",
 			ip:   "",
 			expect: func(t *testing.T, d interface{}) {
 				assert := assert.New(t)
diff --git a/pkg/util/hostutils/host_utils_test.go b/pkg/util/hostutils/host_utils_test.go
new file mode 100644
index 00000000..503d790b
--- /dev/null
+++ b/pkg/util/hostutils/host_utils_test.go
@@ -0,0 +1,33 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package hostutils
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestHostname(t *testing.T) {
+	hostname := hostname()
+	assert.NotEmpty(t, hostname)
+}
+
+func TestFQDNHostname(t *testing.T) {
+	fqdn := fqdnHostname()
+	assert.NotEmpty(t, fqdn)
+}
diff --git a/pkg/util/net/iputils/ip_utils_test.go b/pkg/util/net/iputils/ip_utils_test.go
index ca7bce83..fde86137 100644
--- a/pkg/util/net/iputils/ip_utils_test.go
+++ b/pkg/util/net/iputils/ip_utils_test.go
@@ -17,22 +17,27 @@
 package iputils
 
 import (
-	"fmt"
+	"net"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
-func TestIsIPv4(t *testing.T) {
-	assert.True(t, IsIPv4("30.225.24.222"))
-	assert.False(t, IsIPv4("30.225.24.2222"))
-}
-
 func TestExternalIPv4(t *testing.T) {
 	ip, err := externalIPv4()
 	assert.Nil(t, err)
+	assert.NotEmpty(t, ip)
+}
 
-	fmt.Println(ip)
+func Test_sortIP(t *testing.T) {
+	var addrs = []net.IP{[]byte("2.3.4.5"), []byte("1.2.3.4"), []byte("3.4.5.6")}
+	sortIP(addrs)
+	assert.EqualValues(t, addrs, []net.IP{[]byte("1.2.3.4"), []byte("2.3.4.5"), []byte("3.4.5.6")})
 
-	assert.NotEmpty(t, ip)
+	var normalAddrs = []net.IP{[]byte("2.3.4.5"), []byte("1.2.3.4"), []byte("3.4.5.6")}
+	var preferAddrs = []net.IP{[]byte("5.3.4.5"), []byte("4.2.3.4"), []byte("6.4.5.6")}
+	sortIP(preferAddrs)
+	sortIP(normalAddrs)
+	assert.EqualValues(t, append(preferAddrs, normalAddrs...), []net.IP{[]byte("4.2.3.4"), []byte("5.3.4.5"), []byte("6.4.5.6"), []byte("1.2.3.4"),
+		[]byte("2.3.4.5"), []byte("3.4.5.6")})
 }
diff --git a/scheduler/config/dynconfig_test.go b/scheduler/config/dynconfig_test.go
index 0718a379..5280311d 100644
--- a/scheduler/config/dynconfig_test.go
+++ b/scheduler/config/dynconfig_test.go
@@ -113,7 +113,10 @@ func TestDynconfigGet_ManagerSourceType(t *testing.T) {
 			tc.mock(mockManagerClient.EXPECT())
 
 			d, err := NewDynconfig(dc.ManagerSourceType, "", []dc.Option{
-				dc.WithManagerClient(NewManagerClient(mockManagerClient, uint(1))),
+				dc.WithManagerClient(NewManagerClient(mockManagerClient, &Config{
+					Manager: &ManagerConfig{SchedulerClusterID: uint(1)},
+					Server:  &ServerConfig{Host: "foo"},
+				})),
 				dc.WithCachePath(cachePath),
 				dc.WithExpireTime(tc.expire),
 			}...)
diff --git a/test/tools/stress/main.go b/test/tools/stress/main.go
index 358ba624..0f05d29e 100644
--- a/test/tools/stress/main.go
+++ b/test/tools/stress/main.go
@@ -129,7 +129,7 @@ loop:
 }
 
 func debug() {
-	debugAddr := fmt.Sprintf("%s:%d", iputils.HostIP, 18066)
+	debugAddr := fmt.Sprintf("%s:%d", iputils.IPv4, 18066)
 	viewer.SetConfiguration(viewer.WithAddr(debugAddr))
 	if err := statsview.New().Start(); err != nil {
 		log.Println("stat view start failed", err)