Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to understand Container Log processing and log-driver implementation in Docker

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >

Share

Shulou(Shulou.com)05/31 Report--

This article introduces how to understand the container log processing and log-driver implementation in Docker. The content is very detailed. Interested friends can refer to it. I hope it will be helpful to you.

Summary

The editor will analyze how docker daemon collects container logs and sends them through the configured log-driver from the point of view of docker (1.12.6) source code, and introduces a zmq-loger implemented in the good Rain Cloud gang with an example.

Reading preparation

(1) first of all, you need to recognize the following keywords:

Stdout:

Standard output, the stream of data written by the process.

Stderr:

Error output, the stream of error data written by the process.

Child processes:

A process created by a process (parent process) that integrates most of the properties of the parent process and can be guarded and managed by the parent process.

(2) you need to know about the form of the log generated by the process:

There are two kinds of output ways for the process to generate logs, one is to write to the file. The other is to write directly to stdout or stderr, such as php's echo python's print golang's fmt.Println ("), and so on.

(3) do you know the relationship between docker-daemon and running container? A container is a special process that is created and started by docker daemon, so container is a child of docker daemon. Guarded and managed by docker daemon. So the stdout of container can be obtained by docker daemon. Based on this theory, we analyze the docker daemon-related code.

Docker-daemon about log source code analysis container instance source code # / container/container.go:62type CommonContainer struct {StreamConfig * stream.Config...} # / container/stream/streams.go:26type Config struct {sync.WaitGroup stdout * broadcaster.Unbuffered stderr * broadcaster.Unbuffered stdin io.ReadCloser stdinPipe io.WriteCloser}

Find the corresponding code shown above, which shows that each container instance has several properties stdout,stderr,stdin, as well as the pipeline stdinPipe. Here we talk about stdinPipe, where standard input will be run when the container is started with the-I parameter, and daemon will be able to write standard input to the container using this pipe.

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

Let's imagine the above illustration, if it is you, how do you achieve log collection and forwarding?

# / container/container.go:312func (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:978func (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}

The first method is to find log-driver for container. First, call: logger.GetLogDriver (cfg.Type) based on the log-driver category configured by the container to return a method type:

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

The essence is to find the logdriver plug-in registered from the factory class, and the specific source code is analyzed below. After getting the c method, the build call parameters are specifically some information about the container. Then call the c method to return driver. Driver is an interface type, so let's see what we can do:

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

There are three simple methods, and it's easy to understand that Log () sends a log message to driver,Close () for a close operation (depending on the implementation). In other words, if we implement a logdriver ourselves, we only need to implement the above three methods and register them in the logger factory class. Let's take a look at / daemon/logger/factory.go

The second way is to process the log, get the log driver, and create a Copier, as the name implies, copy the log from stdout and stderr to logger driver, respectively. Let's take a look at the key implementations:

# / daemon/logger/copir.go:41func (c * Copier) copySrc (name string, src io.Reader) {defer c.copyJobs.Done () reader: = bufio.NewReader (src) for {select {case 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}}

Each row of data is read, a message is built, and the log method of logdriver is called to send it to driver for processing.

Log driver registrar

The source code at / daemon/logger/factory.go implements the registry of real-time logging driver, with several important methods (one mentioned above):

# / daemon/logger/factory.go:21func (lf * logdriverFactory) register (name string, c Creator) error {if lf.driverRegistered (name) {return fmt.Errorf ("logger: logdriver named'% s'is already registered" Name)} lf.m.Lock () lf.registry [name] = c lf.m.Unlock () return nil} # / daemon/logger/factory.go:39func (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}

It seems simple to add a Creator method type to a map structure and a LogOptValidator to another map. Here, note the locking operation.

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

This is mainly used to verify the parameters of driver. The startup parameters of dockerd and docker include:-- log-opt

Good Rain Cloud helps you to implement a log-driver based on zmq.

The whole process of docker daemon managing logdriver and processing logs has been fully analyzed above. I'm sure you already know better. Let's take zmq-driver as an example to show how we implement our own driver. Receive the container log directly.

Above we have talked about several methods that need to be implemented in a log-driver. We can look at the existing implementations of driver located in the / daemon/logger directory, such as fluentd,awslogs, and so on. Let's analyze the specific code of zmq-driver:

/ / define a struct that contains a zmq socket type ZmqLogger struct {writer * zmq.Socket containerId string tenantId string serviceId string felock sync.Mutex} / / defines the method of the init method calling the logger registry to register the current driver// and parameter verification methods. Func init () {if err: = logger.RegisterLogDriver (name, New); err! = nil {logrus.Fatal (err)} if err: = logger.RegisterLogOptValidator (name, ValidateLogOpt) Err! = nil {logrus.Fatal (err)}} / / implement a Creator method registration logdriver.// mentioned above. Here create a new zmq socket to build an instance 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} / / implement the Log method Here we use zmq socket to send log messages / / it must be noted here that zmq socket is not thread safe, we know / / this method may be called by two threads (copy stdout and skin stderr) / / locks must be used to ensure thread safety. Otherwise, an error will occur. 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} / / implement the Close method This is used to shut down zmq socket. / / also pay attention to thread safety. This method is called by the container to close the co-program. 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} / / to verify the parameter, we use the parameter to pass in the address of 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} on how to understand container log processing in Docker and the log-driver implementation are shared here. I hope the above content can be of some help to you and learn more knowledge. If you think the article is good, you can share it for more people to see.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Servers

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report