久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

如何理解Docker中的容器日志處理與log

191次閱讀
沒有評論

共計 7125 個字符,預計需要花費 18 分鐘才能閱讀完成。

這篇文章給大家介紹如何理解 Docker 中的容器日志處理與 log-driver 實現,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

概要

丸趣 TV 小編將從 docker(1.12.6)源碼的角度分析 docker daemon 怎么將容器的日志收集出來并通過配置的 log-driver 發送出去,并結合示例介紹了好雨云幫中實現的一個 zmq-loger。

閱讀準備  

(1)首先你需要認知以下幾個關鍵詞:

stdout:
標準輸出,進程寫數據的流。

stderr:
錯誤輸出,進程寫錯誤數據的流。

子進程:
由一個進程(父進程)創建的進程,集成父進程大部分屬性,同時可以被父進程守護和管理。

(2)你需要知道關于進程產生日志的形式:
進程產生日志有兩類輸出方式,一類是寫入到文件中。另一類是直接寫到 stdout 或者 stderr,例如 php 的 echo python 的 print golang 的 fmt.Println()等等。
(3)是否知道 docker-daemon 與運行中 container 的關系?一個 container 就是一個特殊的進程,它是由 docker daemon 創建并啟動,因此 container 是 docker daemon 的子進程。由 docker daemon 守護和管理。因此 container 的 stdout 能夠被 docker daemon 獲取到?;诖死碚摚覀儊矸治?docker daemon 相關代碼。

docker-daemon 關于日志源碼分析 container 實例源碼

# /container/container.go:62
type CommonContainer struct{
 StreamConfig *stream.Config
 ...
# /container/stream/streams.go:26
type Config struct {
 sync.WaitGroup
 stdout *broadcaster.Unbuffered
 stderr *broadcaster.Unbuffered
 stdin io.ReadCloser
 stdinPipe io.WriteCloser
}

找到如上所示對應的代碼,顯示了每一個 container 實例都有幾個屬性 stdout,stderr,stdin, 以及管道 stdinPipe。這里說下 stdinPipe, 當容器使用 - i 參數啟動時標準輸入將被運行,daemon 將能夠使用此管道向容器內寫入標準輸入。

![2017011930658image2017-1-18 17-18-38.png](http://7xqmjb.com1.z0.glb.clouddn.com/2017011930658image2017-1-18 17-18-38.png)

我們試想以上圖例,如果是你,你怎么實現日志收集轉發?

# /container/container.go:312
func (container *Container) StartLogger(cfg containertypes.LogConfig) (logger.Logger, error) {c, err := logger.GetLogDriver(cfg.Type)
 if err != nil {return nil, fmt.Errorf( Failed to get logging factory: %v , err)
 ctx := logger.Context{
 Config: cfg.Config,
 ContainerID: container.ID,
 ContainerName: container.Name,
 ContainerEntrypoint: container.Path,
 ContainerArgs: container.Args,
 ContainerImageID: container.ImageID.String(),
 ContainerImageName: container.Config.Image,
 ContainerCreated: container.Created,
 ContainerEnv: container.Config.Env,
 ContainerLabels: container.Config.Labels,
 DaemonName:  docker ,
 // Set logging file for  json-logger 
 if cfg.Type == jsonfilelog.Name {ctx.LogPath, err = container.GetRootResourcePath(fmt.Sprintf( %s-json.log , container.ID))
 if err != nil {
 return nil, err
 return c(ctx)
#/container/container.go:978
func (container *Container) startLogging() error {
 if container.HostConfig.LogConfig.Type ==  none  {
 return nil // do not start logging routines
 l, err := container.StartLogger(container.HostConfig.LogConfig)
 if err != nil {return fmt.Errorf( Failed to initialize logging driver: %v , err)
 copier := logger.NewCopier(map[string]io.Reader{stdout : container.StdoutPipe(),  stderr : container.StderrPipe()}, l)
 container.LogCopier = copier
 copier.Run()
 container.LogDriver = l
 // set LogPath field only for json-file logdriver
 if jl, ok := l.(*jsonfilelog.JSONFileLogger); ok {container.LogPath = jl.LogPath()
 return nil
}

第一個方法是為 container 查找 log-driver。首先根據容器配置的 log-driver 類別調用:logger.GetLogDriver(cfg.Type)返回一個方法類型:

/daemon/logger/factory.go:9
type Creator func(Context) (Logger, error)

實質就是從工廠類注冊的 logdriver 插件去查找,具體源碼下文分析。獲取到 c 方法后構建調用參數具體就是容器的一些信息。然后使用調用 c 方法返回 driver。driver 是個接口類型,我們看看有哪些方法:

# /daemon/logger/logger.go:61
type Logger interface {Log(*Message) error
 Name() string
 Close() error}

很簡單的三個方法,也很容易理解,Log()發送日志消息到 driver,Close()進行關閉操作(根據不同實現)。也就是說我們自己實現一個 logdriver,只需要實現如上三個方法,然后注冊到 logger 工廠類中即可。下面我們來看 /daemon/logger/factory.go

第二個方法就是處理日志了,獲取到日志 driver, 在創建一個 Copier, 顧名思義就是復制日志,分別從 stdout 和 stderr 復制到 logger driver。下面看看具體關鍵實現:

#/daemon/logger/copir.go:41
func (c *Copier) copySrc(name string, src io.Reader) {defer c.copyJobs.Done()
 reader := bufio.NewReader(src)
 for {
 select {
 case  -c.closed:
 return
 default:
 line, err := reader.ReadBytes(\n)
 line = bytes.TrimSuffix(line, []byte{\n})
 // ReadBytes can return full or partial output even when it failed.
 // e.g. it can return a full entry and EOF.
 if err == nil || len(line)   0 {if logErr := c.dst.Log( Message{Line: line, Source: name, Timestamp: time.Now().UTC()}); logErr != nil {logrus.Errorf( Failed to log msg %q for logger %s: %s , line, c.dst.Name(), logErr)
 if err != nil {
 if err != io.EOF {logrus.Errorf( Error scanning log stream: %s , err)
 return
}

每讀取一行數據,構建一個消息,調用 logdriver 的 log 方法發送到 driver 處理。

日志 driver 注冊器

位于 /daemon/logger/factory.go 的源碼實現即時日志 driver 的注冊器,其中幾個重要的方法(上文已經提到一個):

# /daemon/logger/factory.go:21
func (lf *logdriverFactory) register(name string, c Creator) error {if lf.driverRegistered(name) {return fmt.Errorf( logger: log driver named  %s  is already registered , name)
 lf.m.Lock()
 lf.registry[name] = c
 lf.m.Unlock()
 return nil
# /daemon/logger/factory.go:39
func (lf *logdriverFactory) registerLogOptValidator(name string, l LogOptValidator) error {lf.m.Lock()
 defer lf.m.Unlock()
 if _, ok := lf.optValidator[name]; ok {return fmt.Errorf( logger: log validator named  %s  is already registered , name)
 lf.optValidator[name] = l
 return nil
}

看起來很簡單,就是將一個 Creator 方法類型添加到一個 map 結構中,將 LogOptValidator 添加到另一個 map 這里注意加鎖的操作。

#/daemon/logger/factory.go:13
type LogOptValidator func(cfg map[string]string) error

這個主要是驗證 driver 的參數 ,dockerd 和 docker 啟動參數中有:–log-opt

好雨云幫自己實現一個基于 zmq 的 log-driver

上文已經完整分析了 docker daemon 管理 logdriver 和處理日志的整個流程。相信你已經比較明白了。下面我們以 zmq-driver 為例講講我們怎么實現自己的 driver。直接接收容器的日志。
上文我們已經談了一個 log-driver 需要實現的幾個方法。我們可以看看位于 /daemon/logger 目錄下的已有的 driver 的實現,例如 fluentd,awslogs 等。下面我們來分析 zmq-driver 具體的代碼:

// 定義一個 struct, 這里包含一個 zmq 套接字
type ZmqLogger struct {
 writer *zmq.Socket
 containerId string
 tenantId string
 serviceId string
 felock sync.Mutex
// 定義 init 方法調用 logger 注冊器的方法注冊當前 driver
// 和參數驗證方法。func init() {if err := logger.RegisterLogDriver(name, New); err != nil {logrus.Fatal(err)
 if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {logrus.Fatal(err)
// 實現一個上文提到的 Creator 方法注冊 logdriver.
// 這里新建一個 zmq 套接字構建一個實例
func New(ctx logger.Context) (logger.Logger, error) {zmqaddress := ctx.Config[zmqAddress]
 puber, err := zmq.NewSocket(zmq.PUB)
 if err != nil {
 return nil, err
 var (env = make(map[string]string)
 tenantId string
 serviceId string
 for _, pair := range ctx.ContainerEnv {p := strings.SplitN(pair,  = , 2)
 //logrus.Errorf(ContainerEnv pair: %s , pair)
 if len(p) == 2 {key := p[0]
 value := p[1]
 env[key] = value
 tenantId = env[TENANT_ID]
 serviceId = env[SERVICE_ID]
 if tenantId ==   {
 tenantId =  default 
 if serviceId ==   {
 serviceId =  default 
 puber.Connect(zmqaddress)
 return  ZmqLogger{
 writer: puber,
 containerId: ctx.ID(),
 tenantId: tenantId,
 serviceId: serviceId,
 felock: sync.Mutex{},}, nil
// 實現 Log 方法,這里使用 zmq socket 發送日志消息
// 這里必須注意,zmq socket 是線程不安全的,我們知道
// 本方法可能被兩個線程 (復制 stdout 和膚質 stderr) 調用 // 必須使用鎖保證線程安全。否則會發生錯誤。func (s *ZmqLogger) Log(msg *logger.Message) error {s.felock.Lock()
 defer s.felock.Unlock()
 s.writer.Send(s.tenantId, zmq.SNDMORE)
 s.writer.Send(s.serviceId, zmq.SNDMORE)
 if msg.Source ==  stderr  {s.writer.Send(s.containerId+ :  +string(msg.Line), zmq.DONTWAIT)
 } else {s.writer.Send(s.containerId+ :  +string(msg.Line), zmq.DONTWAIT)
 return nil
// 實現 Close 方法,這里用來關閉 zmq socket。// 同樣注意線程安全,調用此方法的是容器關閉協程。func (s *ZmqLogger) Close() error {s.felock.Lock()
 defer s.felock.Unlock()
 if s.writer != nil {return s.writer.Close()
 return nil
func (s *ZmqLogger) Name() string {
 return name
// 驗證參數的方法,我們使用參數傳入 zmq pub 的地址。func ValidateLogOpt(cfg map[string]string) error {
 for key := range cfg {
 switch key {
 case zmqAddress:
 default:
 return fmt.Errorf(unknown log opt  %s  for %s log driver , key, name)
 if cfg[zmqAddress] ==   {return fmt.Errorf( must specify a value for log opt  %s , zmqAddress)
 return nil
}

關于如何理解 Docker 中的容器日志處理與 log-driver 實現就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-08-17發表,共計7125字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 通州市| 清流县| 方城县| 岱山县| 桑植县| 凤冈县| 南川市| 金门县| 广德县| 武强县| 临夏市| 大悟县| 玉屏| 河西区| 石家庄市| 普兰店市| 安龙县| 莆田市| 福贡县| 清水河县| 南充市| 祁连县| 台安县| 岳西县| 巧家县| 库车县| 葫芦岛市| 南安市| 定南县| 南江县| 陆良县| 大英县| 霍城县| 简阳市| 新昌县| 花莲县| 容城县| 陕西省| 丹棱县| 宝鸡市| 富阳市|