Реализация Graphic kubernetes команда ядро исполнения

K8S команда завершается apiserver, kubelet, CRI, грузчик и другими компоненты вместе, что является наиболее сложным переключением протокола и различным связанным с потоком копирование, давайте посмотрим на ключе, чтобы достичь этого, хотя код более, но не будет развиваться Он также должен быть в состоянии читать, я желаю вам удачи

1. Основные понятия

Есть много протоколов, связанных с обработкой выполняется в командах K8S, давайте посмотрим вместе эти основные понятия, связанные с обработкой протокола

1,1 Http Соглашение Подключение и Upgrade

image.png

HTTP / 1.1 разрешено на той же конверсии протокола линии связи достигается за счетом головки заголовка в связи с обновлением, это просто разрешено осуществлять связь с использованием других протоколов о создании ссылки с помощью HTTP, который является K8S команды ключ к достижению обновления соглашения

1.2 Http код 101 статусы протоколов

image.png

В протоколе HTTP, в дополнении к нашему общим HTTP1.1, а также поддерживает WebSocket / SPDY других соглашений, как, что услуги и клиент полных различные протоколы через HTTP включили его, в первую очередь здесь является первым элементом 101 (Switching Protocal ) код состояния, что сервер сказать клиенту мы перешли на Uprage до протокола, определенного для связи (мультиплексирование текущей ссылки)

1.3 поток протокола SPDY

image.png

Протокол SPDY Google разработан протокол сеансового уровня TCP в SPDY протокола НТТР запрос / ответ называется поток, и поддерживает связь TCP Мультиплексирования между множеством одновременно метила указанный поток идентификатор, это просто поддерживает множество ответа на запрос обработки одновременно в одной линии, и независимо друг от друга, K8S команда осуществляется в основном через поток передачи сообщений

1.4 Перенаправление дескриптор файла

image.png

В процессе выполнения Linux обычно состоит из трех FD: стандартный ввод, стандартный вывод, стандартная ошибку, K8S соответствующая команда FD будет перенаправлена ​​для того, чтобы команда усиления выходного сигнала судна, переадресовать его где? Конечно, мы уже упоминали выше, поток (потому что докер не знакомы, так что это место не гарантирует точность раздела Докер)

1,5 HTTP угонщика

image.png

Между клиента и сервера с помощью кода состояния 101, соединения, upragde тому подобного после завершения преобразования текущей линии связи на основании текущей линии передачи данных не в Соглашении до HTTP1.1, то мы должны обратиться к соответствующей ссылке HTTP пересылки к соответствующему преобразованию протокола, во время выполнения команды K8S, мы получаем соответствующий запрос ссылки ТСР и Response, HTTP через нижнюю часть интерфейса, чтобы получить Hijacker продолжить, чтобы завершить запрошенными

1.6 на основе потока переадресации TCP Duikao

После того, как TCP ReaderWriter приобретены два нижних Hijacker, io.copy может быть сделан непосредственно два копиями соответствующего потока данных, тем самым устраняя необходимость в конверсии протокола apiserver на месте, но непосредственно TCP Duikao потока может быть достигнуто, и результаты запроса переадресации

Вероятно, это основное введение, затем мы пошли к реализации, лежащий в основе бетона, мы начинаем с kubectl части, чтобы развернуть

2.kubectl

Kubectl выполнение команд, разделенные на две части Pod обнаружение легитимности и выполнение команд, тестирование легальности Pod Pod в основном для получения соответствующего определение состояния работает, где мы ориентируемся на участке выполнения команды

2.1 Основные процессы выполнения команд

image.png

Команда ядро ​​делится на два этапа: 1. Построение потока с ссылкой на ссылку 2) в соответствии с протоколом 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 Exec строительство запрос

Мы можем увидеть это место сращивания Url / стручки / {имен} / {podName} / Exec фактически соответствует стручок subresource apiserver выше интерфейсов, то мы можем увидеть конец процесса запроса 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 УСТАНОВЛЕНИЕ поток

image.png

Поток в типе exec.Stream в основном за счет передачи заголовков, которые будут установлены в консультации с серверной

    // 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 играют в процессе выполнения заказа в роли агентства, которое отвечает за запросов между Kubectl и kubelet быть перенаправлены, направляется эта нота в основном базируется на TCP-поток Duikao завершена, поскольку связь между kubectl и kubelet, на самом деле SPDY соглашение, давайте посмотрим на ключ к ее достижению

3.1 Подключение

image.png

Exec является SPDY запрос сначала направляется в Connect Interface, устанавливается соединение интерфейса отвечает с kubelet задним концом линии связи, и в ответ на результат, возвращаемый в интерфейсе Connection, сначала получает информацию через узел, соответствующий Pod, и строит Расположение т.е. 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 Приобретать службу адрес фоновой

Получение адреса в основном построены информации о местоположении, задний конец, информация приобретается к соответствующему узлу хоста и порта Информация в настоящем документе будут сообщены по kubelet и сборке конечного пути к капсуле, то есть, где поле Путь / Exec / {имен} / {podName} / {ИмяКонтейнера}

    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 для повышения инициализации обработчика

Протокол в основном через контроллеры лифта UpgradeAwareHandler, после получения запроса обработчика будет сначала попытаться улучшить протокол, который в основном обнаружение значение соединение заголовок HTTP Upragde, который не реализован, предыдущий kubelet анализа может знать, откуда это, безусловно, верно

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多代码
}

Процесс обновления 3.4 протокола

image.png

логика обработки протокола для повышения больше, здесь делится на несколько секций, чтобы быть последовательно объяснено, главным образом HTTP запроса приобретение, чтобы начать связь, и вперед, и в то же время удерживая две ссылки, копирование и TCP потоки по ссылке

3.4.1 установить связи с kubelet

Первый шаг заключается в расширении соглашения с kubelet бэкендом установить связь, здесь будет kubelet прислал мне копию запроса и отправляется в бэкэнде kubelet, а также получить ссылку здесь HTTP налаженного и kubelet, проведенная за при необходимости использования потока Duikao, обратите внимание этот факт HTTP запрос код статуса ответа 101, то есть на самом деле построен kubelet SPDY обработчик протокола для связи

        // 构建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 запрос Запрос Hijack

Этот запрос является фактически SPDY соглашение, после получения ссылки на дно с помощью Hijack, вам необходимо сначала направить запрос на вышеприведенном kubelet тем самым вызывая kubelet отправить запрос, чтобы установить связь за поток ЗАПИСИ здесь результаты пересылаются 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 двусторонний поток Duikao

После двух вышеупомянутых шагов, два пришел иметь apiserver HTTP ссылки, HTTP протокол не является, потому что это не непосредственно apiserver операции, но только с использованием потокового Duikao пути вперед запросы и ответы

    // 双向拷贝链接
    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

Выполнение команды на Kubelet в основном зависят от CRI.RuntimeService осуществляется, kubelet ответственности только за пересылку соответствующего запроса, и в конечном итоге создать прокси-сервер пересылает последующие запросы потока, чтобы завершить свою миссию

4.1 Запуск основного процесса

Основной первичный процесс является получением команды для выполнения, то соответствующее Под нового обнаружением и вызова host.GetExec возвращает соответствующий URL-адрес, а затем последующие запросы будут завершены proxyStream, мы начали тщательный шаг за шагом

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 возвращает результаты

host.GetExec в конечном счете вызывает в runtimeService то есть запрос Exec cri.RuntimeService выполнения в интерфейс, возвращает интерфейс адрес, который / Exec / {маркер}, не выполняются в это время и фактическая команда просто создает запрос выполнения команды больше ничего

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)
}

Окончательный крику фактически вызов EXEC интерфейс, мы будем игнорировать то, что вернулись конкретные интерфейсы, оставшаяся логика смотреть 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

Здесь мы можем видеть, что мы видели раньше UpgradeAwareHandler, но это задняя часть реализации URL ехес возвращения URL-адрес, а затем оставшаяся часть apiserver внутри почти так же, как для потоковой передачи HTTP связь между двумя бить

Мы думаем об этом месте запроса и ответ, на самом деле, связан с соответствующим apiserver kubelet Учрежденного SPDY голова на этой ссылке, помните это место, в это время, чтобы продолжать строить какую-либо связь с фоновым звеном, задний конец фактически SPDY сервер протокола, до сих пор мы все еще последняя часть ссылка возвращается в конце концов, что, кто соответствующий контроллер, быть частью следующего раздела 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 в конечном счете несет ответственность за выполнение команды, позиция выполнения команды фактически выполняются, которая также включает в себя много протокола обработки, связанной с операцией, давайте посмотрим на ключе к ее достижению

Зарегистрировано 5,1 DockerRuntime

В приведенном выше, мы называем RuntimeService интерфейса Exec в конце концов нашел следующий код в kubelet, создать и запустить DockerServer

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

Где Start функция внутри, зарегистрировали следующие два RuntimeService писал КПГР друзьям все знают, что это на самом деле зарегистрированный соответствуют реализациям RPC интерфейса, который в конечном счете, мы называем интерфейс DockerService

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

5.2 Реализация DockerService из Exec

Exec окончательной реализации можно найти на самом деле под названием интерфейс возвращает streamingServer GetExec а / Exec / {маркер} интерфейса

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

Мы продолжаем отслеживать streamingServer GetExec интерфейс можно рассматривать следующим образом, в конечном итоге построить URL = / EXEC / {маркер} Обратите внимание, что в настоящее время на самом деле хранится в запросе кэша запроса

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 параметры выполнения команды 5.3 сборки

Во-первых, давайте через лексемы кэш до запроса, а затем команда запроса через Exec, строительство StreamOpts, и в конечном итоге вызова выполнение ServeExec, следующим шагом является наиболее трудно понять, в частности, высокоэнергетический фронт

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 на двух основных этапов: 1) Создать выполнение потока 2) запроса, тем более сложным, в основном сосредоточены в части, чтобы создать поток, мы отмечаем параметры раздела ExecInContainer, передавая CTX связанный дескриптор файла, полученный путем создания потока поток, createStreams внутри есть два протокола для достижения WebSocket и HTTPS, где мы проанализировать основной HTTPS (мы используем протокол HTTPS используется kubectl)

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 Создание HTTPS потока

Создание потока я резюмировать на следующие этапы: 1) быть HTTPS рукопожатия 2) Протокол SPDY повышен до 3-х) ждет потока создания, мы обратимся, чтобы увидеть

1. Заполните HTTPS рукопожатия

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

2. Обновление протокола

    // 流管道
    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
    })

Существует механизм ключа для передачи и функция обратного вызова функ streamch заднего прохода, установленной здесь будет создать сервер после ссылки, и была введен в контроллере FUNC является функция обратного вызова, которая каждый раз после установления связи, если приобретение чтобы соответствующий поток будет добавлен к StreamCh, следующее является наиболее сложной частью сетевой обработки, слишком сложно, так что это еще одна открытый бар

5.6 SPDY поток создания

image.png

В целом процесс выглядит простым, является первым крупным переключение протокола выполняется в соответствии с запросом, а затем возвращает 101, построен и SPDY запрос обработки на основе текущей линии связи, а затем ждать, чтобы быть установлен kubectl поток передается через apiserver, полный связи по потоку друг с другом поток создания

5.6.1 Протокол для усиления ответа

Первый шаг будет заключаться в расширении протокола ответа, в котором мы обращаем внимание на несколько ключевых части, SPDY соглашения, а также 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 установить spdyServer

spdyConn, err := NewServerConnection(connWithBuf, newStreamHandler)

Окончательный будет нести ответственность за установление новых связей через newConnection

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

    return newConnection(spdyConn, newStreamHandler), nil
}

Здесь мы можем видеть, чтобы запустить фоновый сервер для обработки запросов ссылки

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 Подавать

1. Первые старты goroutine ответственное множество запросов обработки, количество рабочего здесь 5, размер очереди 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, переключающий кадр, будет хэш, чтобы выбрать соответствующий frameQueues StreamID очередей кадров в соответствии с

        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. Считать рамку считывания, и которые должны быть переданы в поток с помощью выше 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)
    }
}

Следующий раздел потребления потока

5,7 Ожидание установления потока

Поток ожидания для создания в основном достигается за счет заголовков внутри StreamType, там говорит соответствующий stdinStream и соответствующий SPDY внутри потока связывания, другие типы тоже


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 CRI адаптер выполняет

Вызов трассировки цепи, может рассматриваться как окончательный вызов, в конечном счете, точка execHandler.ExecInContainer интерфейса для выполнения команд в сосуде

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 Основной процесс выполнения команды

Суть потока порядка делится на две части: 1) Создание миссии EXEC 2) Начало EXEC миссии

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
}

вызовы командного интерфейса 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 команды

image.png

Команда ядра выполнения для достижения двух основных этапов: 1) сначала посылает запрос выполнения Exec 2) и запускает соответствующий результат Exec приобретение, сложный или SPDY связанный логический поток

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 запрос выполнения команды

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

подключение Запрос 5.10.2 Отправить

Здесь HiHijackConn с аналогичными функциями ранее введены, ядро ​​через создание ссылки HTTP, а затем обновить соглашение, которое является ссылкой основной TCP сопп, и ссылки на соответствующий ток устанавливается Keepliave 30s, у нас есть это базируемый двусторонний канал связи SPDY

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 устанавливают Duikao текут

На данный момент в kubelet мы получаем к вершине там и apiserver поток потока создано, чтобы выполнить команду с серверным, то вам нужно всего лишь скопировать два потоки прямой передача данных может быть достигнута

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
}

статус выполнения 5.10.4 обнаружения

image.png
После завершения выполнения команды происходит, он будет проводить проверку состояния выполнения каждого 2s звонка, если он найден выполнен, он будет выходить

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. Заключение

image.png

Весь процесс выполнения заказа, но это все еще довольно сложно, в основном лежит в той части коммутации сетевого протокола, мы можем видеть, что на самом деле на протяжении всего процесса, основаны на протоколе SPDY для выполнения, и мы также можем видеть в этой части CRI.RuntimeService Потоковая обработка запроса на самом деле больше, чем goroutine одновременного восхищения при больших конструкциях коровы, то, что написано в том месте, добро пожаловать, чтобы обсудить, спасибо шишек можно посмотреть здесь

kubernetes Study Notes Адрес: https://www.yuque.com/baxiaoshi/tyado3

рекомендация

отblog.51cto.com/srexin/2485630