Gráfico kubernetes aplicación núcleo de ejecución de comandos

K8S el comando se completa con el apiserver, kubelet, cri, cargador de muelle y otros componentes juntos, que es el más complejo de conmutación de protocolo y varios stream copy relacionada, veamos la clave para lograr que aunque el código más, pero no se desarrollará también debe ser capaz de leer, les deseo buena suerte

1. Conceptos básicos

Existen muchos protocolos asociados con el procesamiento realizado en los comandos K8S, veamos juntos estos conceptos básicos relacionados con el procesamiento del protocolo

1.1 Http acuerdo de conexión y de actualización

image.png

HTTP / 1.1 se permite en la misma conversión de protocolo de enlace se consigue por la cabeza de cabecera en relación con la actualización, se permite simplemente para comunicarse con otros protocolos en el establecimiento de un enlace a través de HTTP, que es comando K8S la clave para lograr la ampliación del contrato de

código de estado de protocolo 1.2 Http 101

image.png

En el protocolo HTTP, además de nuestro HTTP1.1 común, también es compatible con otros acuerdos WebSocket / SPDY, la forma en que el servicio al cliente y completos protocolos diferentes a través de http conmutada que, en primer lugar aquí es el primer elemento 101 (Switching Protocal ) código de estado que el servidor de decirle al cliente que cambiamos a Uprage un protocolo definido para la comunicación (multiplexación de enlace actual)

1,3 secuencia del protocolo SPDY

image.png

protocolo de Google SPDY se desarrolla protocolo de capa de sesión TCP en el protocolo SPDY la petición HTTP / Respuesta de llama Stream, y apoya el enlace TCP multiplexación entre una pluralidad de simultáneamente marcado por la corriente de la corriente-id, es simplemente soporta una pluralidad de respuesta de solicitud de procesamiento simultáneamente en un solo enlace, y de forma independiente uno del otro, K8S el comando se lleva a cabo principalmente a través de la corriente de paso de mensajes

1.4 descriptor de archivo de redirección

image.png

En proceso de ejecución Linux lo general consiste en tres FD: entrada estándar, salida estándar, error estándar, K8S el comando FD correspondiente será redirigido a fin de comando ganancia de la salida del vaso, redirigirlo donde? Por supuesto que hemos mencionado anteriormente, la corriente (porque no están familiarizados con cargador de muelle, así que este lugar no garantiza la exactitud de la sección del estibador)

1,5 http El secuestrador

image.png

Entre cliente y servidor mediante código de estado 101, la conexión, upragde similares después de la finalización de la conversión del enlace actual basado en el enlace de transmisión de datos actual no está de acuerdo antes de la HTTP1.1, entonces debemos recurrir al enlace correspondiente http el reenvío a la correspondiente conversión de protocolo, durante la ejecución de los K8S de comando, se obtiene la correspondiente tcp solicitud de enlace y de respuesta, http a través de la parte inferior de la interfaz para obtener secuestrador seguir para completar el pedido

1.6 basado en el flujo de reenvío tcp Duikao

Después de tcp ReaderWriter adquirida por la parte inferior dos Secuestrador, io.copy se puede hacer directamente por las dos copias de la secuencia de datos correspondiente, eliminando así la necesidad de apiserver conversión de protocolo en su lugar, sino directamente por tcp Duikao flujo se puede lograr y los resultados de la solicitud de reenvío

Probablemente éstos introducción básica, y luego fuimos a ver a la realización concreta subyacente, partimos de parte kubectl para profundizar

2.kubectl

Kubectl ejecutar comandos dividido en dos partes de detección de la legitimidad de la vaina y ejecución de comandos, poniendo a prueba la legalidad Pod principalmente para obtener la detección del estado correspondiente está en ejecución, donde nos centramos en la sección de ejecución de comandos

2.1 procesos centrales de ejecución de comandos

image.png

El comando núcleo está dividido en dos pasos: 1. Construir Stream para enlace a enlace 2) por el protocolo SPDY

func (*DefaultRemoteExecutor) Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
    exec, err := remotecommand.NewSPDYExecutor(config, method, url)
    if err != nil {
        return err
    }
    return exec.Stream(remotecommand.StreamOptions{
        Stdin:             stdin,
        Stdout:            stdout,
        Stderr:            stderr,
        Tty:               tty,
        TerminalSizeQueue: terminalSizeQueue,
    })
}

2.2 construcción petición exec

Podemos ver este lugar empalmado Url / vainas / espacio de nombres {} / {} podName / exec en realidad corresponde a la vaina subrecurso apiserver por encima de las interfaces, a continuación, podemos ver el final del proceso de la solicitud apiserver

    // 创建一个exec
        req := restClient.Post().
            Resource("pods").
            Name(pod.Name).
            Namespace(pod.Namespace).
            SubResource("exec")
        req.VersionedParams(&corev1.PodExecOptions{
            Container: containerName,
            Command:   p.Command,
            Stdin:     p.Stdin,
            Stdout:    p.Out != nil,
            Stderr:    p.ErrOut != nil,
            TTY:       t.Raw,
        }, scheme.ParameterCodec)
return p.Executor.Execute("POST", req.URL(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue)

2.3 ESTABLECIMIENTO Corriente

image.png

Arroyo en el tipo de exec.Stream principalmente a través de la transferencia de encabezados que se establezcan, en consulta con el extremo del servidor

    // set up stdin stream
    if p.Stdin != nil {
        headers.Set(v1.StreamType, v1.StreamTypeStdin)
        p.remoteStdin, err = conn.CreateStream(headers)
        if err != nil {
            return err
        }
    }

    // set up stdout stream
    if p.Stdout != nil {
        headers.Set(v1.StreamType, v1.StreamTypeStdout)
        p.remoteStdout, err = conn.CreateStream(headers)
        if err != nil {
            return err
        }
    }

    // set up stderr stream
    if p.Stderr != nil && !p.Tty {
        headers.Set(v1.StreamType, v1.StreamTypeStderr)
        p.remoteStderr, err = conn.CreateStream(headers)
        if err != nil {
            return err
        }
    }

3.APIServer

APIServer juego en el proceso de ejecución de la orden en el papel de la agencia, que se encarga de las solicitudes entre Kubectl y kubelet que se remitirá, remitido esta nota se basa principalmente en stream tcp Duikao completar porque la comunicación entre el kubectl y kubelet, en realidad acuerdo SPDY, veamos la clave para lograrlo

3.1 Conexión

image.png

Exec es petición SPDY se envía primero a la Interfaz de Conexión, se establece la conexión de interfaz responsable con el extremo trasero kubelet del enlace, y en respuesta el resultado devuelto en la interfaz de conexión, primero obtiene la información de nodo correspondiente a través de la vaina, y construye Ubicación es decir dirección y transporte enlaces de back-end Kubelet

func (r *ExecREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
    execOpts, ok := opts.(*api.PodExecOptions)
    if !ok {
        return nil, fmt.Errorf("invalid options object: %#v", opts)
    }
    // 返回对应的地址,以及建立链接
    location, transport, err := pod.ExecLocation(r.Store, r.KubeletConn, ctx, name, execOpts)
    if err != nil {
        return nil, err
    }
    return newThrottledUpgradeAwareProxyHandler(location, transport, false, true, true, responder), nil
}

3.2 adquirir la dirección del servicio de back-end

La adquisición de dirección principalmente construida información de ubicación extremo posterior, la información se adquiere a un nodo correspondiente del anfitrión y Información del puerto en el presente documento será informado por kubelet, y montaje de la ruta de acceso final a la vaina, es decir, donde el campo Path / exec / {namespace} / {podName} / {} nombreContenedor

    loc := &url.URL{
        Scheme:   nodeInfo.Scheme,
        Host:     net.JoinHostPort(nodeInfo.Hostname, nodeInfo.Port),   // node的端口
        Path:     fmt.Sprintf("/%s/%s/%s/%s", path, pod.Namespace, pod.Name, container),    // 路径
        RawQuery: params.Encode(),
    }

3.3 protocolo para mejorar la inicialización manejador

Protocolo principalmente a través de los controladores de ascensor UpgradeAwareHandler, después de recibir el controlador de solicitudes primero tratará de mejorar el protocolo, que es principalmente un valor de conexión de detección http cabecera Upragde que no está implementado, el anterior kubelet análisis puede saber de dónde es cierto

func newThrottledUpgradeAwareProxyHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired, interceptRedirects bool, responder rest.Responder) *proxy.UpgradeAwareHandler {

    handler := proxy.NewUpgradeAwareHandler(location, transport, wrapTransport, upgradeRequired, proxy.NewErrorResponder(responder))
    handler.InterceptRedirects = interceptRedirects && utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StreamingProxyRedirects)
    handler.RequireSameHostRedirects = utilfeature.DefaultFeatureGate.Enabled(genericfeatures.ValidateProxyRedirects)
    handler.MaxBytesPerSec = capabilities.Get().PerConnectionBandwidthLimitBytesPerSec
    return handler
}

func (h *UpgradeAwareHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    // 如果协议提升成功,则由该协议完成
    if h.tryUpgrade(w, req) {
        return
    }
    // 省略N多代码
}

proceso de actualización 3,4 protocolo

image.png

la lógica de procesamiento de protocolos para mejorar más, aquí se divide en varias secciones para ser sucesivamente se explica, principalmente HTTP solicitud de adquisición para iniciar el enlace, y hacia delante y al mismo tiempo la celebración de los dos enlaces, copiando y flujos TCP en el enlace

3.4.1 establecer vínculos con kubelet

El primer paso es mejorar acuerdo con kubelet backend establecer un enlace, aquí se kubelet me envió una copia de la solicitud y la envía al kubelet back-end, y también tendrá un enlace aquí http de un kubelet establecido y, llevada a cabo tras cuando necesidad de uso fluya Duikao, nota de este solicitud del código de estado de respuesta http hecho es 101, es decir, la realmente construido kubelet SPDY un controlador de protocolo para comunicarse

        // 构建http请求
        req, err := http.NewRequest(method, location.String(), body)
        if err != nil {
            return nil, nil, err
        }

        req.Header = header

        // 发送请求建立链接
        intermediateConn, err = dialer.Dial(req)
        if err != nil {
            return nil, nil, err
        }

        // Peek at the backend response.
        rawResponse.Reset()
        respReader := bufio.NewReader(io.TeeReader(
            io.LimitReader(intermediateConn, maxResponseSize), // Don't read more than maxResponseSize bytes.
            rawResponse)) // Save the raw response.
            // 读取响应信息
        resp, err := http.ReadResponse(respReader, nil)

3.4.2 Solicitud solicitud Secuestro

Esta petición es en realidad un acuerdo SPDY, después de obtener un enlace a la parte inferior por secuestro, es necesario primero hacia delante la solicitud a la kubelet anterior iniciando así kubelet enviar una solicitud para establecer un vínculo detrás de corriente es escribir aquí los resultados remitidos kubelet

    requestHijackedConn, _, err := requestHijacker.Hijack()
    // Forward raw response bytes back to client.
    if len(rawResponse) > 0 {
        klog.V(6).Infof("Writing %d bytes to hijacked connection", len(rawResponse))
        if _, err = requestHijackedConn.Write(rawResponse); err != nil {
            utilruntime.HandleError(fmt.Errorf("Error proxying response from backend to client: %v", err))
        }
    }

3.4.3 flujo bidireccional Duikao

Después de los dos pasos anteriores, los dos llegaron a tener apiserver http enlace, el protocolo HTTP no es porque no es directamente apiserver operación, pero sólo mediante la transmisión de manera Duikao a transmitir las peticiones y las respuestas

    // 双向拷贝链接
    go func() {
        var writer io.WriteCloser
        if h.MaxBytesPerSec > 0 {
            writer = flowrate.NewWriter(backendConn, h.MaxBytesPerSec)
        } else {
            writer = backendConn
        }
        _, err := io.Copy(writer, requestHijackedConn)
        if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
            klog.Errorf("Error proxying data from client to backend: %v", err)
        }
        close(writerComplete)
    }()

    go func() {
        var reader io.ReadCloser
        if h.MaxBytesPerSec > 0 {
            reader = flowrate.NewReader(backendConn, h.MaxBytesPerSec)
        } else {
            reader = backendConn
        }
        _, err := io.Copy(requestHijackedConn, reader)
        if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
            klog.Errorf("Error proxying data from backend to client: %v", err)
        }
        close(readerComplete)
    }()

4.kubelet

Ejecutar comandos en Kubelet depende principalmente de CRI.RuntimeService lleva a cabo, kubelet sólo es responsable de la transmisión de una solicitud correspondiente, y, finalmente, crear un proxy reenvía las solicitudes posteriores Stream, para completar su misión

4.1 Ejecutar el proceso principal

El proceso primario principal es obtener el comando a ser ejecutado, entonces el correspondiente nuevo la detección y la llamada Pod host.GetExec devuelve un URL correspondiente, y luego las solicitudes posteriores será completado por proxyStream, comenzamos un paso a fondo a paso

func (s *Server) getExec(request *restful.Request, response *restful.Response) {
    // 获取执行命令
    params := getExecRequestParams(request)
    streamOpts, err := remotecommandserver.NewOptions(request.Request)
    // 获取pod的信息
    pod, ok := s.host.GetPodByName(params.podNamespace, params.podName)
    podFullName := kubecontainer.GetPodFullName(pod)
    url, err := s.host.GetExec(podFullName, params.podUID, params.containerName, params.cmd, *streamOpts)
    proxyStream(response.ResponseWriter, request.Request, url)
}

4,2 Exec devuelve los resultados

host.GetExec llama finalmente a la solicitud de ejecución runtimeService decir Exec cri.RuntimeService a la interfaz, la interfaz vuelve una dirección que es / exec / {} testigo, no se lleva a cabo en este momento y la orden real, simplemente crea una solicitud de ejecución de comandos nada más

func (m *kubeGenericRuntimeManager) GetExec(id kubecontainer.ContainerID, cmd []string, stdin, stdout, stderr, tty bool) (*url.URL, error) {
    // 省略请求构造
    // 执行命令
    resp, err := m.runtimeService.Exec(req)
    return url.Parse(resp.Url)
}

El CRI final se llama en realidad la interfaz ejecutivo, ignoraremos lo devuelven las interfaces específicas, la lógica restante viendo kubelet

func (c *runtimeServiceClient) Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error) {
    err := c.cc.Invoke(ctx, "/runtime.v1alpha2.RuntimeService/Exec", in, out, opts...)
}

4.3 proxyStream

image.png

Aquí podemos ver es que hemos visto antes UpgradeAwareHandler, pero esta es la parte final de la aplicación url ejecutivo de la vuelta de la URL, y luego la parte que queda dentro apiserver casi igual, para el streaming http vínculo entre los dos latido

Pensamos en este lugar de solicitudes y respuestas, de hecho, está vinculado con el correspondiente kubelet apiserver establecida es la cabeza SPDY en este enlace, recuerda este lugar, en este momento para continuar la construcción de cualquier conexión con el enlace de servicios de fondo, la parte trasera es en realidad un SPDY servidor de protocolo, hasta ahora siguen siendo la última parte es el enlace devuelto al final es lo que es el controlador correspondiente, ser parte de la próxima sección cri

// proxyStream proxies stream to url.
func proxyStream(w http.ResponseWriter, r *http.Request, url *url.URL) {
    // TODO(random-liu): Set MaxBytesPerSec to throttle the stream.
    handler := proxy.NewUpgradeAwareHandler(url, nil /*transport*/, false /*wrapTransport*/, true /*upgradeRequired*/, &responder{})
    handler.ServeHTTP(w, r)
}

5.CRI

CRI.RuntimeService en última instancia, responsable de la ejecución del comando, la posición de la ejecución del comando se ejecuta realmente, lo que también implica una gran cantidad de protocolo de procesamiento relacionado con la operación, veamos la clave para lograrlo

El 5,1 DockerRuntime registrada

En lo anterior, llamamos RuntimeService de interfaz Exec finalmente encontró el siguiente código en kubelet, crear y lanzar un DockerServer

ds, err := dockershim.NewDockerService(kubeDeps.DockerClientConfig, crOptions.PodSandboxImage, streamingConfig,
dockerServer := dockerremote.NewDockerServer(remoteRuntimeEndpoint, ds)
        if err := dockerServer.Start(); err != nil {
            return err
        }

Donde Comenzar función dentro, registrado los dos RuntimeService siguiente, escribió amigos GRPC todos sabemos, esto es realmente una corresponden registrado para implementar la interfaz RPC que en última instancia, que llamamos la interfaz es DockerService

    runtimeapi.RegisterRuntimeServiceServer(s.server, s.service)
    runtimeapi.RegisterImageServiceServer(s.server, s.service)

5.2 DockerService realización de Exec

implementación final Exec se puede encontrar en realidad llamados interfaz regresa streamingServer GetExec a / exec / {} símbolo de interfaz

func (ds *dockerService) Exec(_ context.Context, req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
    // 执行Exec请求
    return ds.streamingServer.GetExec(req)
}

Continuamos el seguimiento streamingServer GetExec interfaz se puede ver de la siguiente manera, con el tiempo construir una url = / exec / {} símbolo, Tenga en cuenta que la corriente se almacena en caché la solicitud Solicitud

func (s *server) GetExec(req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
    // 生成token
    token, err := s.cache.Insert(req)
    return &runtimeapi.ExecResponse{
        Url: s.buildURL("exec", token),
    }, nil
}

Exec parámetros de ejecución de comandos 5.3 build

En primer lugar, vamos a llegar a través de la caché de símbolo antes de Solicitud, y luego a través de orden de petición ejecutivo, la construcción de StreamOpts, y terminan llamando la ejecución ServeExec, el siguiente paso es el más difícil de entender, en parte, la parte frontal de alta energía

func (s *server) serveExec(req *restful.Request, resp *restful.Response) {
    // 获取token
    token := req.PathParameter("token")
    // 缓存请求
    cachedRequest, ok := s.cache.Consume(token)
    // 构建exec参数s
    exec, ok := cachedRequest.(*runtimeapi.ExecRequest)

    streamOpts := &remotecommandserver.Options{
        Stdin:  exec.Stdin,
        Stdout: exec.Stdout,
        Stderr: exec.Stderr,
        TTY:    exec.Tty,
    }

    // 构建ServerExec执行请求
    remotecommandserver.ServeExec(
        resp.ResponseWriter,
        req.Request,
        s.runtime,
        "", // unused: podName
        "", // unusued: podUID
        exec.ContainerId,
        exec.Cmd,
        streamOpts,
        s.config.StreamIdleTimeout,
        s.config.StreamCreationTimeout,
        s.config.SupportedRemoteCommandProtocols)
}

5.4 ServerExec

ServerExec en dos pasos clave: 1) Crear una ejecución de la ruta 2) de la solicitud, el más complejo se concentra principalmente en parte para crear una corriente, observamos la sección de parámetros ExecInContainer, pasando el descriptor de archivo relacionado ctx obtenido mediante la creación de una corriente Stream, createStreams interior hay dos protocolos para lograr WebSocket y https, donde se analizan los principales https (utilizamos el protocolo https es kubectl usado)

func ServeExec(w http.ResponseWriter, req *http.Request, executor Executor, podName string, uid types.UID, container string, cmd []string, streamOpts *Options, idleTimeout, streamCreationTimeout time.Duration, supportedProtocols []string) {
    // 创建serveExec
    ctx, ok := createStreams(req, w, streamOpts, supportedProtocols, idleTimeout, streamCreationTimeout)

    defer ctx.conn.Close()

    // 获取执行,这是一个阻塞的过程,err会获取当前的执行是否成功, 这里将ctx里面的信息,都传入进去,对应的其实就是各种流
    err := executor.ExecInContainer(podName, uid, container, cmd, ctx.stdinStream, ctx.stdoutStream, ctx.stderrStream, ctx.tty, ctx.resizeChan, 0)

}

5.5 Creación de corriente HTTPS

establecimiento corriente que se resume en los siguientes pasos: 1) Ser https apretón de manos 2) protocolo actualizado a 3) espera SPDY para el flujo de creación, nos volvemos a ver

1. Completar el https apretón de manos

protocol, err := httpstream.Handshake(req, w, supportedStreamProtocols)

2. Protocolo de actualización

    // 流管道
    streamCh := make(chan streamAndReply)

    upgrader := spdy.NewResponseUpgrader()
    // 构建spdy链接
    conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream, replySent <-chan struct{}) error {
        // 当新请求建立之后,会追加到streamch
        streamCh <- streamAndReply{Stream: stream, replySent: replySent}
        return nil
    })

Hay un mecanismo clave para la transferencia y el paso de devolución de llamada función func volver streamch, establecida aquí creará un servidor después de un vínculo, se introdujo a una func controlador es una función de devolución de llamada que cada vez después de que el establecimiento de un enlace, si la adquisición a la corriente correspondiente se añadirá a StreamCh, la siguiente es la parte más compleja del procesamiento de la red, demasiado complicado, lo que es todavía un bar abierto solo

5.6 corriente SPDY de establecimiento

image.png

En general, el aspecto de proceso simple, es la primera importante de conmutación de protocolo se realiza de acuerdo con la solicitud y devuelve 101, construido y solicitud SPDY procesamiento basado en el enlace de corriente, y luego esperar que se establezca la corriente kubectl transmite a través de apiserver, comunicación de flujo completo uno con el otro corriente de establecimiento

5.6.1 Protocolo para potenciar la respuesta

El primer paso será para mejorar el protocolo de respuesta, cuando se presta atención a varias partes clave, de acuerdo SPDY, así como el código de estado 101

    // 协议
    hijacker, ok := w.(http.Hijacker)
    if !ok {
        errorMsg := fmt.Sprintf("unable to upgrade: unable to hijack response")
        http.Error(w, errorMsg, http.StatusInternalServerError)
        return nil
    }

    w.Header().Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
    // sydy协议
    w.Header().Add(httpstream.HeaderUpgrade, HeaderSpdy31)
    w.WriteHeader(http.StatusSwitchingProtocols)

5.6.2 establecer spdyServer

spdyConn, err := NewServerConnection(connWithBuf, newStreamHandler)

La final será el responsable de establecer nuevos vínculos a través NewConnection

func NewServerConnection(conn net.Conn, newStreamHandler httpstream.NewStreamHandler) (httpstream.Connection, error) {
    // 创建一个新的链接, 通过一个已经存在的网络链接
    spdyConn, err := spdystream.NewConnection(conn, true)

    return newConnection(spdyConn, newStreamHandler), nil
}

Aquí podemos ver es para iniciar un servidor de fondo para procesar las solicitudes de enlace

func newConnection(conn *spdystream.Connection, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection {
    c := &connection{conn: conn, newStreamHandler: newStreamHandler}
    // 当建立链接后,进行syn请求创建流的时候,会调用newSpdyStream
    go conn.Serve(c.newSpdyStream)
    return c
}

5.6.3 Servir

1. En primer lugar se inicia goroutine pluralidad responsable de procesar las solicitudes, el número de trabajadores aquí es 5, el tamaño de la cola es de 20,

frameQueues := make([]*PriorityFrameQueue, FRAME_WORKERS)
    for i := 0; i < FRAME_WORKERS; i++ {
        frameQueues[i] = NewPriorityFrameQueue(QUEUE_SIZE)

        // Ensure frame queue is drained when connection is closed
        go func(frameQueue *PriorityFrameQueue) {
            <-s.closeChan
            frameQueue.Drain()
        }(frameQueues[i])

        wg.Add(1)
        go func(frameQueue *PriorityFrameQueue) {
            // let the WaitGroup know this worker is done
            defer wg.Done()

            s.frameHandler(frameQueue, newHandler)
        }(frameQueues[i])
    }

2. synStreamFrame monitor, marco desviador, será un hash para seleccionar un correspondiente frameQueues StreamID colas de tramas de acuerdo con el

        case *spdy.SynStreamFrame:
            if s.checkStreamFrame(frame) {
                priority = frame.Priority
                partition = int(frame.StreamId % FRAME_WORKERS)
                debugMessage("(%p) Add stream frame: %d ", s, frame.StreamId)
                // 添加到对应的StreamId对应的frame里面
                s.addStreamFrame(frame)
            } else {
                debugMessage("(%p) Rejected stream frame: %d ", s, frame.StreamId)
                continue

                // 最终会讲frame push到上面的优先级队列里面
                frameQueues[partition].Push(readFrame, priority)

3. Lea el marco de lectura y ser transmitido a la corriente por el anterior StreamCH newHandler

func (s *Connection) frameHandler(frameQueue *PriorityFrameQueue, newHandler StreamHandler) {
    for {
        popFrame := frameQueue.Pop()
        if popFrame == nil {
            return
        }

        var frameErr error
        switch frame := popFrame.(type) {
        case *spdy.SynStreamFrame:
            frameErr = s.handleStreamFrame(frame, newHandler)
    }
}

La siguiente sección de la consumo de flujo

5,7 de espera establecer Corriente

Corriente de esperar a que el establecimiento de logrado principalmente a través de encabezados dentro streamType, no habla el correspondiente stdinStream y SPDY correspondiente dentro de la corriente de la unión, otros tipos, también


func (*v3ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) {
    ctx := &context{}
    receivedStreams := 0
    replyChan := make(chan struct{})
    stop := make(chan struct{})
    defer close(stop)
WaitForStreams:
    for {
        select {
        case stream := <-streams:
            streamType := stream.Headers().Get(api.StreamType)
            switch streamType {
            case api.StreamTypeError:
                ctx.writeStatus = v1WriteStatusFunc(stream)
                go waitStreamReply(stream.replySent, replyChan, stop)
            case api.StreamTypeStdin:
                ctx.stdinStream = stream
                go waitStreamReply(stream.replySent, replyChan, stop)
            case api.StreamTypeStdout:
                ctx.stdoutStream = stream
                go waitStreamReply(stream.replySent, replyChan, stop)
            case api.StreamTypeStderr:
                ctx.stderrStream = stream
                go waitStreamReply(stream.replySent, replyChan, stop)
            case api.StreamTypeResize:
                ctx.resizeStream = stream
                go waitStreamReply(stream.replySent, replyChan, stop)
            default:
                runtime.HandleError(fmt.Errorf("unexpected stream type: %q", streamType))
            }
        case <-replyChan:
            receivedStreams++
            if receivedStreams == expectedStreams {
                break WaitForStreams
            }
        case <-expired:
            // TODO find a way to return the error to the user. Maybe use a separate
            // stream to report errors?
            return nil, errors.New("timed out waiting for client to create streams")
        }
    }

    return ctx, nil
}

5.8 adaptador comando IRC realiza

cadena de invocación de rastreo puede ser visto como la llamada final, finalmente, una interfaz de punto execHandler.ExecInContainer para ejecutar comandos en un recipiente

func (a *criAdapter) ExecInContainer(podName string, podUID types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
    // 执行command
    return a.Runtime.Exec(container, cmd, in, out, err, tty, resize)
}
func (r *streamingRuntime) Exec(containerID string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
    //  执行容器
    return r.exec(containerID, cmd, in, out, err, tty, resize, 0)
}

// Internal version of Exec adds a timeout.
func (r *streamingRuntime) exec(containerID string, cmd []string, in io.Reader, out, errw io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
    // exechandler
    return r.execHandler.ExecInContainer(r.client, container, cmd, in, out, errw, tty, resize, timeout)
}

5.9 principal proceso de ejecución de comandos

El punto principal del flujo de órdenes se divide en dos partes: 1) Crear una misión ejecutivo 2) Inicio ejecutivo de misión

func (*NativeExecHandler) ExecInContainer(client libdocker.Interface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
    // 在容器中执行命令
    done := make(chan struct{})
    defer close(done)

    // 执行命令
    createOpts := dockertypes.ExecConfig{
        Cmd:          cmd,
        AttachStdin:  stdin != nil,
        AttachStdout: stdout != nil,
        AttachStderr: stderr != nil,
        Tty:          tty,
    }
    // 创建执行命令任务
    execObj, err := client.CreateExec(container.ID, createOpts)

    startOpts := dockertypes.ExecStartCheck{Detach: false, Tty: tty}
    // 这里我们可以看到我们将前面获取到的stream的封装,都作为FD传入到容器的执行命令里面去了
    streamOpts := libdocker.StreamOptions{
        InputStream:  stdin,
        OutputStream: stdout,
        ErrorStream:  stderr,
        RawTerminal:  tty,
        ExecStarted:  execStarted,
    }
    // 执行命令
    err = client.StartExec(execObj.ID, startOpts, streamOpts)

    ticker := time.NewTicker(2 * time.Second)
    defer ticker.Stop()
    count := 0
    for {
        //  获取执行结果
        inspect, err2 := client.InspectExec(execObj.ID)
        if !inspect.Running {
            if inspect.ExitCode != 0 {
                err = &dockerExitError{inspect}
            }
            break
        }
        <-ticker.C
    }

    return err
}

llamadas de interfaz de comandos de Docker

func (cli *Client) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error) {
    resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, config, nil)
    return response, err
}

5.10 comando de aplicación núcleo de ejecución

image.png

núcleo de ejecución de comandos para lograr dos pasos principales: 1) envía primera solicitud de ejecución exec 2) y se inicia el correspondiente Corriente resultado adquisición exec, complejo o lógica SPDY asociado

func (d *kubeDockerClient) StartExec(startExec string, opts dockertypes.ExecStartCheck, sopts StreamOptions) error {
    // 启动执行命令, 获取结果
    resp, err := d.client.ContainerExecAttach(ctx, startExec, dockertypes.ExecStartCheck{
        Detach: opts.Detach,
        Tty:    opts.Tty,
    })
    // 将输入流拷贝到输出流, 这里会讲resp里面的结果拷贝到outputSTream里面
    return d.holdHijackedConnection(sopts.RawTerminal || opts.Tty, sopts.InputStream, sopts.OutputStream, sopts.ErrorStream, resp)
}

5.10.1 solicitud de ejecución de comandos

cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, config, headers)

Solicitud de la conexión 5.10.2 Enviar

Aquí HiHijackConn con funciones similares introducidos anteriormente, el núcleo es mediante el establecimiento de un enlace http, y luego actualizar acuerdo, que es el enlace TCP conn subyacente, y un enlace de vuelta a la corriente correspondiente se establece 30s Keepliave, tenemos este un SPDY a base de dos vías enlace de comunicación

func (cli *Client) postHijacked(ctx context.Context, path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) {
    conn, err := cli.setupHijackConn(ctx, req, "tcp")
    return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
}

5.10.3 establecer el flujo Duikao

En este punto de kubelet de llegar a la cima allí y apiserver Corriente Corriente establecido para ejecutar el comando con el back-end, a continuación, sólo tiene que copiar dos corrientes se puede lograr la transmisión directa de los datos

func (d *kubeDockerClient) holdHijackedConnection(tty bool, inputStream io.Reader, outputStream, errorStream io.Writer, resp dockertypes.HijackedResponse) error {
    receiveStdout := make(chan error)
    if outputStream != nil || errorStream != nil {
        // 将响应结果拷贝到outputstream里面
        go func() {
            receiveStdout <- d.redirectResponseToOutputStream(tty, outputStream, errorStream, resp.Reader)
        }()
    }

    stdinDone := make(chan struct{})
    go func() {
        if inputStream != nil {
            io.Copy(resp.Conn, inputStream)
        }
        resp.CloseWrite()
        close(stdinDone)
    }()

    return nil
}

estado de ejecución 5.10.4 detección

image.png
Después se produce la finalización de la ejecución del comando, que llevará a cabo una inspección del estado de aplicación de todas las campanas 2s, si se comprueba ejecutado, sino que terminará

func (*NativeExecHandler) ExecInContainer(client libdocker.Interface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
    ticker := time.NewTicker(2 * time.Second)
    defer ticker.Stop()
    count := 0
    for {
        //  获取执行结果
        inspect, err2 := client.InspectExec(execObj.ID)
        if err2 != nil {
            return err2
        }
        if !inspect.Running {
            if inspect.ExitCode != 0 {
                err = &dockerExitError{inspect}
            }
            break
        }
        <-ticker.C
    }

    return err
}

func (cli *Client) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) {
    resp, err := cli.get(ctx, "/exec/"+execID+"/json", nil, nil)
    return response, err
}

6. Resumen

image.png

Todo el proceso de ejecución de la orden, pero todavía es bastante complejo, sobre todo radica en que parte de la conmutación de protocolos de red, podemos ver que, de hecho, en todo el proceso, se basan en el protocolo SPDY para llevar a cabo, y también podemos ver en esa parte del CRI.RuntimeService procesamiento de flujos de la solicitud es en realidad más que admiración concurrente goroutine en los diseños grandes de la vaca, lo que está escrito en el lugar equivocado, dan la bienvenida a discutir, gracias a los peces gordos pueden ver aquí

kubernetes Estudio Notas Dirección: https://www.yuque.com/baxiaoshi/tyado3

Supongo que te gusta

Origin blog.51cto.com/srexin/2485630
Recomendado
Clasificación