그래픽는 Kubernetes 명령 실행 코어 구현

명령이 가장 복잡한 프로토콜 스위칭 및 다양한 관련 스트림 사본입니다 apiserver, kubelet, CRI, 고정 표시기와 함께 다른 구성 요소에 의해 완료 K8S, 우리가 코드를 더하지만,하지만 개발하지 않을 달성의 열쇠를 살펴 보자 또한 당신에게 행운을 기원합니다, 읽을 수 있어야

1. 기본 개념

K8S 명령을 수행 가공과 관련이 많은 프로토콜이있다,의 함께 프로토콜 처리와 관련된 이러한 기본 개념을 살펴 보자

1.1의 HTTP 연결 동의 및 업그레이드

의 image.png

HTTP / 1.1은 업그레이드와 관련하여 헤더 헤드에 의해 달성되는 동일한 링크 프로토콜 변환 허용됩니다, 단순히 K8S 명령 인 HTTP를 통해 링크의 설립에 다른 프로토콜을 사용하여 통신 할 수있다 계약 업그레이드를 달성 키

1.2의 HTTP 프로토콜 상태 코드 101

의 image.png

HTTP 프로토콜에서, 우리의 공동 HTTP1.1뿐만 아니라, 또한 우선 여기의 Protocal 전환의 첫 번째 요소 (101) (있다, HTTP를 통해 해당 서비스와 클라이언트 완전한 서로 다른 프로토콜을 전환하는 방법, 웹 소켓 / SPDY 다른 계약을 지원합니다 서버는 우리가 통신을 위해 정의 된 프로토콜까지 Uprage로 전환 클라이언트에게 있음) 상태 코드 () 현재 링크를 다중화

1.3 SPDY 프로토콜 스트림

의 image.png

구글 SPDY 프로토콜은 HTTP 요청 / 응답 스트림이라고 프로토콜 SPDY에 TCP 세션 계층 프로토콜을 개발하고, TCP 연결이 동시에 스트림 스트림-ID에 의해 분류 된 복수의 사이 다중화 지지체는, 그것이 단순히 단일 링크에서 동시에 처리 요청 응답하는 복수의 지원하고, 서로 독립적으로, 명령은 주로 메시지 전달의 흐름을 통해 수행된다 K8S

1.4 리디렉션 파일 설명

의 image.png

리눅스 실행 처리는 일반적으로 세 FD 구성에서 : 표준 입력, 표준 출력, 표준 오차를, 대응 FD 명령 용기 출력의 이득을 명령하기 위해 리다이렉트한다 K8S 여기서 다시 지정할? (이 장소가 부두 노동자 섹션의 정확성을 보증하지 않도록와 고정 표시기에 익숙하지 않은 때문에) 물론 우리는 스트림, 위에서 언급 한의

1.5 HTTP를 공중 납치

의 image.png

상태 코드 (101), 연결하여 클라이언트와 서버 사이에, 현재의 데이터 전송 링크를 기반으로 현재 링크의 변환 완료 후 같은를 upragde은 HTTP1.1, 우리는 해당 HTTP 링크를 설정해야하기 전에 동의하지 않습니다 명령 K8S 실행하는 동안, 해당 프로토콜 변환에 전달, 우리는 요청 완료 계속 하이 잭 범인을 얻기 위해 인터페이스의 하단을 통해 해당 링크 요청 TCP 및 응답, HTTP를 얻기

1.6 Duikao TCP 전송의 흐름에 기초

TCP의 ReaderWriter 두 바닥 납치범 취득한 후 io.copy함으로써 장소에 프로토콜 변환 apiserver의 필요성을 제거, 해당 데이터 스트림의 두 복사본하여 수행하지만, 직접 TCP 의해 수 Duikao 흐름을 달성 할 수 있으며 발송 요청의 결과

아마 이러한 기본적인 소개, 우리가 기본이되는 구체적인 실현을 보러 갔다, 우리가 드릴 다운 kubectl 부분에서 시작

2.kubectl

적법성 포드 포드 주로 대응하는 상태 검출 우리는 명령 실행 부에 집중하는 경우, 실행되는 테스트 구하는 두 부분 포드 정당성 검출 및 명령 실행으로 분할 명령을 실행 Kubectl

2.1 명령 실행 코어 프로세스

의 image.png

1. SPDY 프로토콜에 의해 링크 2)에 링크 스트림을 구축 : 핵심 명령은 두 단계로 나누어 져 있습니다

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 간부 요청 건설

우리는 / 간부 실제로 인터페이스 위의 포드 하위 리소스 apiserver에 해당하는, 우리가 처리 요청 apiserver의 끝을 볼 수있는 URL / 포드 / {네임 스페이스} / {podName} 접합이 곳을 볼 수 있습니다

    // 创建一个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

이 메모 전달 전달 될 Kubectl와 kubelet 사이의 요청을 담당하는 기관의 역할 순서의 구현 과정에서 APIServer 재생이 주로 TCP 스트림을 기반으로 Duikao이 완료 때문에 kubectl 사이의 통신 및 kubelet, 실제로 SPDY 계약은 우리가 그것을 달성하기 위해 키를 살펴 보자

3.1 연결

의 image.png

Exec에서 SPDY 요청이 먼저 연결 인터페이스로 전송되면, 링크의 kubelet 후단 책임 인터페이스 연결이 설정되며, 반응 결과는 연결 인터페이스에 포드를 통해 대응하는 제 취득 노드 정보를 반환 한 위치, 즉 구축 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에서보고되며, 포드의 마지막 경로 조립, 즉 경로 필드 / 간부 / {공간}에 대응하는 노드를 취득 / {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 링크에 흐름에 대한 설명 여러 부분으로 나누어 져, 더 향상시킬

kubelet로 링크를 설정 3.4.1

첫 번째 단계는 kubelet와 계약을 향상 백엔드 링크를 설정하는 것입니다, 여기 나에게 요청의 사본을 보내 백엔드 kubelet로 전송하고, 또한 여기에 HTTP 설립 및 kubelet의 링크를 얻을 것이다, 뒤에 실시 kubelet합니다 사용할 필요가 Duikao 흐름되면이 사실 HTTP 요청 응답 상태 코드가 실제로 통신하는 프로토콜 핸들러 SPDY kubelet 구성 즉, 101 도와

        // 构建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 요청 요청 공중 납치

이 요청은 실제로 SPDY 계약입니다, 공중 납치하여 하단에 링크를 획득 한 후, 당신은 위의 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 결국 인터페이스에 Exec을 runtimeService 즉 cri.RuntimeService 실행 요구에 대한 호출 인터페이스 리턴 어드레스 즉 / 간부 / {토큰} 현재 수행되지 않으므로 실제 명령은 단순히 명령 실행 요구를 작성 아무것도 더

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

마지막 CRI가 실제로 간부 인 인터페이스를 호출, 우리는 특정 인터페이스가 반환 무엇을 무시하는 것,보고 나머지 논리 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 전에 본 적이 있다는 것입니다,하지만이 둘 사이 스트리밍 HTTP 링크, 다음 내부 거의 단지 등의 나머지 부분 apiserver URL의 반환의 URL 간부 구현의 뒤쪽 끝이며, 비트

우리는이 곳 요청 및 응답, 사실, 설립 대응 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

상기에서, 우리는 만들고 DockerServer를 시작, Exec에서 인터페이스의 RuntimeService 결국 kubelet에 다음 코드를 발견 전화

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

어디이 궁극적으로 우리가 인터페이스가 DockerService입니다 부르는 RPC 인터페이스를 구현하는 등록 된 대응이 실제로 시작 기능의 내부가, 다음과 같은 두 가지 RuntimeService 등록 grpc 친구에게 모든 노하우를 썼습니다

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

Exec에서 5.2 DockerService 실현

Exec에서 최종 구현은 실제로라는 streamingServer GetExec 인터페이스를 반환 A / 간부 / {토큰} 인터페이스를 찾을 수 있습니다

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

우리는 결국 현재 실제로 캐시 요청 요청에 저장되어있는 URL = / 간부 / {토큰}, 참고를 구축, 인터페이스는 다음과 같이 볼 수있다 streamingServer GetExec을 추적 계속

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 빌드 명령 실행 매개 변수

먼저, 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) 실행을 만들기가 더 복잡한 주로 스트림을 만들기 위해 부분적으로 농축하고, 우리는 스트림을 생성하여 얻어진 CTX 관련 파일 디스크립터를 전달 매개 부 ExecInContainer 유의 웹 소켓 및 HTTPS, 우리는 주요 HTTPS를 분석 곳 (우리가 HTTPS 프로토콜을 사용 kubectl 인 사용)을 달성하기 위해 두 개의 프로토콜이있는 내부 스트림, createStreams

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

콜백 함수가되는 링크 후 서버를 만들 것입니다 여기에 설립 전송 및 콜백 기능 FUNC의 streamch의 백 패스의 핵심 메커니즘이며, 컨트롤러 FUNC에 소개 된 그 링크의 설립 후마다, 인수하는 경우 해당 스트림 StreamCh 추가 될하려면 다음 네트워크 처리의 가장 복잡한 부분은 여전히 ​​단독 오픈 바 정도로 너무 복잡

설립의 5.6 SPDY 스트림

의 image.png

전체 프로세스 보이는 간단한가 구성되고 SPDY 요구가 현재 링크에 기초하여 처리하고 서로, kubectl 스트림 apiserver 통해 송신 완료 유체 연통을 확립하는 것을 기다릴 요청에 따라 수행되는 첫번째 주요 프로토콜 전환되고, 그 후 101 반환 설립의 스트리밍

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)

spdyServer을 설정 5.6.2

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)

판독 프레임을 읽어와 StreamCH newHandler 3. 위의 스트림으로 송신 될

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) 2 임원 임무 작성)을 시작 간부의 임무 : 수주의 주요 포인트는 두 부분으로 나누어 져 있습니다

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
}

부두 노동자의 명령 인터페이스 호출

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)는 먼저 실행 요구 간부 2)을 전송하고 해당 간부 취득 결과 또는 복잡한 논리 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의 CONN 링크 인 계약을, 업그레이드 및 해당 전류에 링크 다시는 Keepliave 30 대를 설정, 우리는이 양방향 통신 링크를 기반으로 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
}

Duikao 흐름 수립 5.10.3

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 연구 노트 주소 : https://www.yuque.com/baxiaoshi/tyado3

추천

출처blog.51cto.com/srexin/2485630