Docker 원본 분석의 용기 로그 처리와log-driver 구현
17590 단어 Docker
본고는 docker(1.12.6) 원본의 측면에서 docker daemon이 용기의 로그를 어떻게 수집하고 설정된log-driver를 통해 보내는지 분석하고 예시와 결합하여 좋은 비구름 방에서 이루어진 zmq-loger를 소개한다.본문을 읽으면 자신의 업무 상황에 적합한log-driver를 실현할 수 있습니다.
읽기 준비
이 글은 Golang 코드를 읽고 작성할 수 있는 학우들에게 적합하다.(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이 지키고 관리합니다.따라서 컨테이너의 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 파라미터를 사용하여 시작할 때 표준 입력이 실행되고, 데몬은 이 파이프를 사용하여 용기에 표준 입력을 쓸 수 있습니다.
![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 return
default:
line, err := reader.ReadBytes('
')
line = bytes.TrimSuffix(line, []byte{'
'})
// 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
방법 유형을 하나의 맵 구조에 추가하고, LogOptValidator
을 다른 맵에 추가하면 자물쇠를 채우는 작업에 주의해야 한다.#/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의 작업 원리를 이해하는 데 편리할 수 있다.오늘 우리는 일지 부분을 분석했다.독자들이 이 부분의 기능에 대해 더욱 분명하게 이해할 수 있기를 바란다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
첫 번째 Docker for Mac참고 , 나는 옆에 있는 Mac에 Docker를 설치하고 컨테이너를 세워 보고 싶다. Docker Hub를 처음 사용할 때는 계정을 만들어야 합니다. Docker.dmg을 실행하면 Docker가 설치됩니다. Dock...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.