kubectl exec๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋‚˜์š”?

๋ฉ”๋ชจ. ๋ฒˆ์—ญ: ๊ธฐ์‚ฌ ์ž‘์„ฑ์ž - SAP ์—”์ง€๋‹ˆ์–ด Erkan Erol - ํŒ€ ๊ธฐ๋Šฅ ๋ฉ”์ปค๋‹ˆ์ฆ˜์— ๋Œ€ํ•œ ์—ฐ๊ตฌ๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. kubectl exec, Kubernetes๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ์‚ฌ๋žŒ์—๊ฒŒ ๋งค์šฐ ์นœ์ˆ™ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Š” ์ „์ฒด ์•Œ๊ณ ๋ฆฌ์ฆ˜๊ณผ ํ•จ๊ป˜ Kubernetes ์†Œ์Šค ์ฝ”๋“œ(๋ฐ ๊ด€๋ จ ํ”„๋กœ์ ํŠธ) ๋ชฉ๋ก์„ ์ œ๊ณตํ•˜๋ฏ€๋กœ ํ•„์š”ํ•œ ๋งŒํผ ์ฃผ์ œ๋ฅผ ๊นŠ์ด ์ดํ•ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

kubectl exec๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋‚˜์š”?

์–ด๋Š ๊ธˆ์š”์ผ, ํ•œ ๋™๋ฃŒ๊ฐ€ ๋‚˜์—๊ฒŒ ๋‹ค๊ฐ€์™€ ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ํฌ๋“œ์—์„œ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฌผ์—ˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ด๋™. ๋‚˜๋Š” ๊ทธ์—๊ฒŒ ๋Œ€๋‹ตํ•  ์ˆ˜ ์—†์—ˆ๊ณ  ๊ฐ‘์ž๊ธฐ ๋‚ด๊ฐ€ ์ผ์˜ ๋ฉ”์ปค๋‹ˆ์ฆ˜์— ๋Œ€ํ•ด ์•„๋ฌด๊ฒƒ๋„ ๋ชจ๋ฅธ๋‹ค๋Š” ๊ฒƒ์„ ๊นจ๋‹ฌ์•˜์Šต๋‹ˆ๋‹ค. kubectl exec. ์˜ˆ, ์ €๋Š” ๊ทธ์˜ ์žฅ์น˜์— ๋Œ€ํ•ด ํŠน์ • ์•„์ด๋””์–ด๋ฅผ ๊ฐ–๊ณ  ์žˆ์—ˆ์ง€๋งŒ ๊ทธ ์ •ํ™•์„ฑ์„ 100% ํ™•์‹ ํ•  ์ˆ˜ ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ธ”๋กœ๊ทธ, ๋ฌธ์„œ ๋ฐ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์—ฐ๊ตฌํ•˜๋ฉด์„œ ๋งŽ์€ ๊ฒƒ์„ ๋ฐฐ์› ์œผ๋ฉฐ ์ด ๊ธฐ์‚ฌ์—์„œ๋Š” ์ œ๊ฐ€ ๋ฐœ๊ฒฌํ•˜๊ณ  ์ดํ•ดํ•œ ๋‚ด์šฉ์„ ๊ณต์œ ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๋ญ”๊ฐ€ ๋ฌธ์ œ๊ฐ€ ์žˆ์œผ๋ฉด ์ €์—๊ฒŒ ์—ฐ๋ฝ์ฃผ์„ธ์š” ํŠธ์œ„ํ„ฐ.

ํ›ˆ๋ จ

MacBook์—์„œ ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ๋ณต์ œํ–ˆ์Šต๋‹ˆ๋‹ค. ecomm-ํ†ตํ•ฉ-๋ฐœ๋ ˆ๋ฆฌ๋‚˜/kubernetes-ํด๋Ÿฌ์Šคํ„ฐ. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๊ธฐ๋ณธ ์„ค์ •์ด ํ—ˆ์šฉํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— kubelet'a ๊ตฌ์„ฑ์—์„œ ๋…ธ๋“œ์˜ IP ์ฃผ์†Œ๋ฅผ ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. kubectl exec. ๊ทธ ์ฃผ๋œ ์ด์œ ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—.

  • ๋ชจ๋“  ๊ธฐ๊ณ„ = ๋‚ด MacBook
  • ๋งˆ์Šคํ„ฐ ๋…ธ๋“œ IP = 192.168.205.10
  • IP ์ž‘์—…์ž ๋…ธ๋“œ = 192.168.205.11
  • API ์„œ๋ฒ„ ํฌํŠธ = 6443

๊ตฌ์„ฑ ์š”์†Œ

kubectl exec๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋‚˜์š”?

  • kubectl exec ํ”„๋กœ์„ธ์Šค: "kubectl exec..."๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด ํ”„๋กœ์„ธ์Šค๊ฐ€ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. K8s API ์„œ๋ฒ„์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๋จธ์‹ ์—์„œ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”๋ชจ. ๋ฒˆ์—ญ: ๋˜ํ•œ ์ฝ˜์†” ๋ชฉ๋ก์—์„œ ์ž‘์„ฑ์ž๋Š” "๋ชจ๋“  ๋จธ์‹ "์ด๋ผ๋Š” ์ฃผ์„์„ ์‚ฌ์šฉํ•˜์—ฌ Kubernetes์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๋จธ์‹ ์—์„œ ๋‹ค์Œ ๋ช…๋ น์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Œ์„ ์•”์‹œํ•ฉ๋‹ˆ๋‹ค.
  • API ์„œ๋ฒ„: Kubernetes API์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋งˆ์Šคํ„ฐ ๋…ธ๋“œ์˜ ๊ตฌ์„ฑ ์š”์†Œ์ž…๋‹ˆ๋‹ค. ์ด๋Š” Kubernetes์˜ ์ œ์–ด ํ‰๋ฉด์— ๋Œ€ํ•œ ํ”„๋ŸฐํŠธ ์—”๋“œ์ž…๋‹ˆ๋‹ค.
  • Kubelet: ํด๋Ÿฌ์Šคํ„ฐ์˜ ๋ชจ๋“  ๋…ธ๋“œ์—์„œ ์‹คํ–‰๋˜๋Š” ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. Pod์˜ ์ปจํ…Œ์ด๋„ˆ ์ž‘์—…์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ์ปจํ…Œ์ด๋„ˆ ๋Ÿฐํƒ€์ž„ (์ปจํ…Œ์ด๋„ˆ ๋Ÿฐํƒ€์ž„): ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰์„ ๋‹ด๋‹นํ•˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด์ž…๋‹ˆ๋‹ค. ์˜ˆ: Docker, CRI-O, ์ปจํ…Œ์ด๋„ˆโ€ฆ
  • ์ปค๋„: ์ž‘์—…์ž ๋…ธ๋“œ์˜ OS ์ปค๋„ ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
  • ๋ชฉํ‘œ (ํ‘œ์ ) ์ปจํ…Œ์ด๋„ˆ: Pod์˜ ์ผ๋ถ€์ด๊ณ  ์ž‘์—…์ž ๋…ธ๋“œ ์ค‘ ํ•˜๋‚˜์—์„œ ์‹คํ–‰๋˜๋Š” ์ปจํ…Œ์ด๋„ˆ์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” ๋ฌด์—‡์„ ๋ฐœ๊ฒฌํ–ˆ๋‚˜์š”?

1. ํด๋ผ์ด์–ธํŠธ ์ธก ํ™œ๋™

๋„ค์ž„์ŠคํŽ˜์ด์Šค์— Pod ๋งŒ๋“ค๊ธฐ default:

// any machine
$ kubectl run exec-test-nginx --image=nginx

๊ทธ๋Ÿฐ ๋‹ค์Œ exec ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๊ณ  ์ถ”๊ฐ€ ๊ด€์ฐฐ์„ ์œ„ํ•ด 5000์ดˆ ๋™์•ˆ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.

// any machine
$ kubectl exec -it exec-test-nginx-6558988d5-fgxgg -- sh
# sleep 5000

kubectl ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค(์ด ๊ฒฝ์šฐ pid=8507).

// any machine
$ ps -ef |grep kubectl
501  8507  8409   0  7:19PM ttys000    0:00.13 kubectl exec -it exec-test-nginx-6558988d5-fgxgg -- sh

ํ”„๋กœ์„ธ์Šค์˜ ๋„คํŠธ์›Œํฌ ํ™œ๋™์„ ํ™•์ธํ•˜๋ฉด API ์„œ๋ฒ„(192.168.205.10.6443)์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// any machine
$ netstat -atnv |grep 8507
tcp4       0      0  192.168.205.1.51673    192.168.205.10.6443    ESTABLISHED 131072 131768   8507      0 0x0102 0x00000020
tcp4       0      0  192.168.205.1.51672    192.168.205.10.6443    ESTABLISHED 131072 131768   8507      0 0x0102 0x00000028

์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Kubectl์€ exec ํ•˜์œ„ ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ POST ์š”์ฒญ์„ ์ƒ์„ฑํ•˜๊ณ  REST ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

              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)

(kubectl/pkg/cmd/exec/exec.go)

kubectl exec๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋‚˜์š”?

2. ๋งˆ์Šคํ„ฐ ๋…ธ๋“œ ์ธก์˜ ํ™œ๋™

API ์„œ๋ฒ„ ์ธก์—์„œ๋„ ์š”์ฒญ์„ ๊ด€์ฐฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

handler.go:143] kube-apiserver: POST "/api/v1/namespaces/default/pods/exec-test-nginx-6558988d5-fgxgg/exec" satisfied by gorestful with webservice /api/v1
upgradeaware.go:261] Connecting to backend proxy (intercepting redirects) https://192.168.205.11:10250/exec/default/exec-test-nginx-6558988d5-fgxgg/exec-test-nginx?command=sh&input=1&output=1&tty=1
Headers: map[Connection:[Upgrade] Content-Length:[0] Upgrade:[SPDY/3.1] User-Agent:[kubectl/v1.12.10 (darwin/amd64) kubernetes/e3c1340] X-Forwarded-For:[192.168.205.1] X-Stream-Protocol-Version:[v4.channel.k8s.io v3.channel.k8s.io v2.channel.k8s.io channel.k8s.io]]

HTTP ์š”์ฒญ์—๋Š” ํ”„๋กœํ† ์ฝœ ๋ณ€๊ฒฝ ์š”์ฒญ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. SPDY ๋‹จ์ผ TCP ์—ฐ๊ฒฐ์„ ํ†ตํ•ด stdin/stdout/stderr/spdy-error์˜ ๊ฐœ๋ณ„ "์ŠคํŠธ๋ฆผ"์„ ๋‹ค์ค‘ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

API ์„œ๋ฒ„๋Š” ์š”์ฒญ์„ ์ˆ˜์‹ ํ•˜๊ณ  ์ด๋ฅผ ๋‹ค์Œ์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. PodExecOptions:

// PodExecOptions is the query options to a Pod's remote exec call
type PodExecOptions struct {
        metav1.TypeMeta

        // Stdin if true indicates that stdin is to be redirected for the exec call
        Stdin bool

        // Stdout if true indicates that stdout is to be redirected for the exec call
        Stdout bool

        // Stderr if true indicates that stderr is to be redirected for the exec call
        Stderr bool

        // TTY if true indicates that a tty will be allocated for the exec call
        TTY bool

        // Container in which to execute the command.
        Container string

        // Command is the remote command to execute; argv array; not executed within a shell.
        Command []string
}

(ํŒจํ‚ค์ง€/apis/core/types.go)

ํ•„์š”ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋ ค๋ฉด API ์„œ๋ฒ„๋Š” ์–ด๋–ค Pod์— ์—ฐ๊ฒฐํ•ด์•ผ ํ•˜๋Š”์ง€ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

// ExecLocation returns the exec URL for a pod container. If opts.Container is blank
// and only one container is present in the pod, that container is used.
func ExecLocation(
        getter ResourceGetter,
        connInfo client.ConnectionInfoGetter,
        ctx context.Context,
        name string,
        opts *api.PodExecOptions,
) (*url.URL, http.RoundTripper, error) {
        return streamLocation(getter, connInfo, ctx, name, opts, opts.Container, "exec")
}

(ํŒจํ‚ค์ง€/registry/core/pod/strategy.go)

๋ฌผ๋ก  ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋Š” ๋…ธ๋“œ์— ๋Œ€ํ•œ ์ •๋ณด์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

        nodeName := types.NodeName(pod.Spec.NodeName)
        if len(nodeName) == 0 {
                // If pod has not been assigned a host, return an empty location
                return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
        }
        nodeInfo, err := connInfo.GetConnectionInfo(ctx, nodeName)

(ํŒจํ‚ค์ง€/registry/core/pod/strategy.go)

๋งŒ์„ธ! Kubelet์—๋Š” ์ด์ œ ํฌํŠธ(node.Status.DaemonEndpoints.KubeletEndpoint.Port) API ์„œ๋ฒ„๊ฐ€ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ์œ„์น˜:

// GetConnectionInfo retrieves connection info from the status of a Node API object.
func (k *NodeConnectionInfoGetter) GetConnectionInfo(ctx context.Context, nodeName types.NodeName) (*ConnectionInfo, error) {
        node, err := k.nodes.Get(ctx, string(nodeName), metav1.GetOptions{})
        if err != nil {
                return nil, err
        }

        // Find a kubelet-reported address, using preferred address type
        host, err := nodeutil.GetPreferredNodeAddress(node, k.preferredAddressTypes)
        if err != nil {
                return nil, err
        }

        // Use the kubelet-reported port, if present
        port := int(node.Status.DaemonEndpoints.KubeletEndpoint.Port)
        if port <= 0 {
                port = k.defaultPort
        }

        return &ConnectionInfo{
                Scheme:    k.scheme,
                Hostname:  host,
                Port:      strconv.Itoa(port),
                Transport: k.transport,
        }, nil
}

(ํŒจํ‚ค์ง€/kubelet/client/kubelet_client.go)

๋ฌธ์„œ์—์„œ ๋งˆ์Šคํ„ฐ-๋…ธ๋“œ ํ†ต์‹  > ๋งˆ์Šคํ„ฐ์—์„œ ํด๋Ÿฌ์Šคํ„ฐ๋กœ > apiserver์—์„œ kubelet์œผ๋กœ:

์ด๋Ÿฌํ•œ ์—ฐ๊ฒฐ์€ kubelet์˜ HTTPS ์—”๋“œํฌ์ธํŠธ์—์„œ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ apiserver๋Š” kubelet์˜ ์ธ์ฆ์„œ๋ฅผ ํ™•์ธํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์—ฐ๊ฒฐ์ด "์ค‘๊ฐ„์ž ๊ณต๊ฒฉ"(MITM)์— ์ทจ์•ฝํ•ด์ง‘๋‹ˆ๋‹ค. ์œ„ํ—˜ํ•œ ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ๋„คํŠธ์›Œํฌ ๋ฐ/๋˜๋Š” ๊ณต์šฉ ๋„คํŠธ์›Œํฌ์—์„œ ์ž‘์—…ํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ API ์„œ๋ฒ„๋Š” ์—”๋“œํฌ์ธํŠธ๋ฅผ ์•Œ๊ณ  ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

// Connect returns a handler for the pod exec proxy
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
}

(pkg/registry/core/pod/rest/subresources.go)

๋งˆ์Šคํ„ฐ ๋…ธ๋“œ์—์„œ ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋จผ์ €, ์ž‘์—… ๋…ธ๋“œ์˜ IP๋ฅผ ์•Œ์•„๋ณด์„ธ์š”. ์šฐ๋ฆฌ์˜ ๊ฒฝ์šฐ ์ด๋Š” 192.168.205.11์ž…๋‹ˆ๋‹ค.

// any machine
$ kubectl get nodes k8s-node-1 -o wide
NAME         STATUS   ROLES    AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
k8s-node-1   Ready    <none>   9h    v1.15.3   192.168.205.11   <none>        Ubuntu 16.04.6 LTS   4.4.0-159-generic   docker://17.3.3

๊ทธ๋Ÿฐ ๋‹ค์Œ kubelet ํฌํŠธ(์ด ๊ฒฝ์šฐ 10250)๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

// any machine
$ kubectl get nodes k8s-node-1 -o jsonpath='{.status.daemonEndpoints.kubeletEndpoint}'
map[Port:10250]

์ด์ œ ๋„คํŠธ์›Œํฌ๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ฐจ๋ก€์ž…๋‹ˆ๋‹ค. ์ž‘์—…์ž ๋…ธ๋“œ(192.168.205.11)์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์Šต๋‹ˆ๊นŒ? ๊ทธ๊ฒƒ์€! ํ”„๋กœ์„ธ์Šค๋ฅผ "์ข…๋ฃŒ"ํ•˜๋Š” ๊ฒฝ์šฐ exec, ์‚ฌ๋ผ์ง€๋ฏ€๋กœ exec ๋ช…๋ น์„ ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ api-server์— ์˜ํ•ด ์—ฐ๊ฒฐ์ด ์„ค์ •๋œ ๊ฒƒ์œผ๋กœ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

// master node
$ netstat -atn |grep 192.168.205.11
tcp        0      0 192.168.205.10:37870    192.168.205.11:10250    ESTABLISHED
โ€ฆ

kubectl exec๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋‚˜์š”?

kubectl๊ณผ api-server ๊ฐ„์˜ ์—ฐ๊ฒฐ์€ ์•„์ง ์—ด๋ ค ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ api-server์™€ kubelet์„ ์—ฐ๊ฒฐํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ์—ฐ๊ฒฐ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์ž‘์—…์ž ๋…ธ๋“œ์—์„œ์˜ ํ™œ๋™

์ด์ œ ์ž‘์—…์ž ๋…ธ๋“œ์— ์—ฐ๊ฒฐํ•˜์—ฌ ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์šฐ์„ , ์šฐ๋ฆฌ๋Š” ๊ทธ์™€์˜ ์—ฐ๊ฒฐ๋„ ํ™•๋ฆฝ๋˜์—ˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๋‘ ๋ฒˆ์งธ ์ค„). 192.168.205.10์€ ๋งˆ์Šคํ„ฐ ๋…ธ๋“œ์˜ IP์ž…๋‹ˆ๋‹ค.

 // worker node
  $ netstat -atn |grep 10250
  tcp6       0      0 :::10250                :::*                    LISTEN
  tcp6       0      0 192.168.205.11:10250    192.168.205.10:37870    ESTABLISHED

์šฐ๋ฆฌ ํŒ€์€ ์–ด๋–ป์Šต๋‹ˆ๊นŒ? sleep? ๋งŒ์„ธ, ๊ทธ๋…€๋„ ๊ฑฐ๊ธฐ ์žˆ์–ด์š”!

 // worker node
  $ ps -afx
  ...
  31463 ?        Sl     0:00      _ docker-containerd-shim 7d974065bbb3107074ce31c51f5ef40aea8dcd535ae11a7b8f2dd180b8ed583a /var/run/docker/libcontainerd/7d974065bbb3107074ce31c51
  31478 pts/0    Ss     0:00          _ sh
  31485 pts/0    S+     0:00              _ sleep 5000
  โ€ฆ

๊ทธ๋Ÿฐ๋ฐ ์ž ๊น๋งŒ์š”, kubelet์€ ์–ด๋–ป๊ฒŒ ์ด๊ฒƒ์„ ํ•ด๋ƒˆ์„๊นŒ์š”? kubelet์—๋Š” API ์„œ๋ฒ„ ์š”์ฒญ์šฉ ํฌํŠธ๋ฅผ ํ†ตํ•ด API์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ์—ฌ๋Š” ๋ฐ๋ชฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

// Server is the library interface to serve the stream requests.
type Server interface {
        http.Handler

        // Get the serving URL for the requests.
        // Requests must not be nil. Responses may be nil iff an error is returned.
        GetExec(*runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error)
        GetAttach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error)
        GetPortForward(*runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error)

        // Start the server.
        // addr is the address to serve on (address:port) stayUp indicates whether the server should
        // listen until Stop() is called, or automatically stop after all expected connections are
        // closed. Calling Get{Exec,Attach,PortForward} increments the expected connection count.
        // Function does not return until the server is stopped.
        Start(stayUp bool) error
        // Stop the server, and terminate any open connections.
        Stop() error
}

(ํŒจํ‚ค์ง€/kubelet/server/streaming/server.go)

Kubelet์€ exec ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต ์—”๋“œํฌ์ธํŠธ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

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

(ํŒจํ‚ค์ง€/kubelet/server/streaming/server.go)

ํ˜ผ๋™ํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค. ๋ช…๋ น ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๊ณ  ์—ฐ๊ฒฐ ๋์ ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

type ExecResponse struct {
        // Fully qualified URL of the exec streaming server.
        Url                  string   `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
        XXX_NoUnkeyedLiteral struct{} `json:"-"`
        XXX_sizecache        int32    `json:"-"`
}

(cri-api/pkg/apis/runtime/v1alpha2/api.pb.go)

Kubelet์€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. RuntimeServiceClient, ์ด๋Š” ์ปจํ…Œ์ด๋„ˆ ๋Ÿฐํƒ€์ž„ ์ธํ„ฐํŽ˜์ด์Šค์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค. (์šฐ๋ฆฌ๋Š” ์ด์— ๋Œ€ํ•ด ๋” ์ž์„ธํžˆ ์ผ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์—ฌ๊ธฐ์— - ๋Œ€๋žต. ๋ฒˆ์—ญ):

kubernetes/kubernetes์˜ cri-api์˜ ๊ธด ๋ชฉ๋ก

// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RuntimeServiceClient interface {
        // Version returns the runtime name, runtime version, and runtime API version.
        Version(ctx context.Context, in *VersionRequest, opts ...grpc.CallOption) (*VersionResponse, error)
        // RunPodSandbox creates and starts a pod-level sandbox. Runtimes must ensure
        // the sandbox is in the ready state on success.
        RunPodSandbox(ctx context.Context, in *RunPodSandboxRequest, opts ...grpc.CallOption) (*RunPodSandboxResponse, error)
        // StopPodSandbox stops any running process that is part of the sandbox and
        // reclaims network resources (e.g., IP addresses) allocated to the sandbox.
        // If there are any running containers in the sandbox, they must be forcibly
        // terminated.
        // This call is idempotent, and must not return an error if all relevant
        // resources have already been reclaimed. kubelet will call StopPodSandbox
        // at least once before calling RemovePodSandbox. It will also attempt to
        // reclaim resources eagerly, as soon as a sandbox is not needed. Hence,
        // multiple StopPodSandbox calls are expected.
        StopPodSandbox(ctx context.Context, in *StopPodSandboxRequest, opts ...grpc.CallOption) (*StopPodSandboxResponse, error)
        // RemovePodSandbox removes the sandbox. If there are any running containers
        // in the sandbox, they must be forcibly terminated and removed.
        // This call is idempotent, and must not return an error if the sandbox has
        // already been removed.
        RemovePodSandbox(ctx context.Context, in *RemovePodSandboxRequest, opts ...grpc.CallOption) (*RemovePodSandboxResponse, error)
        // PodSandboxStatus returns the status of the PodSandbox. If the PodSandbox is not
        // present, returns an error.
        PodSandboxStatus(ctx context.Context, in *PodSandboxStatusRequest, opts ...grpc.CallOption) (*PodSandboxStatusResponse, error)
        // ListPodSandbox returns a list of PodSandboxes.
        ListPodSandbox(ctx context.Context, in *ListPodSandboxRequest, opts ...grpc.CallOption) (*ListPodSandboxResponse, error)
        // CreateContainer creates a new container in specified PodSandbox
        CreateContainer(ctx context.Context, in *CreateContainerRequest, opts ...grpc.CallOption) (*CreateContainerResponse, error)
        // StartContainer starts the container.
        StartContainer(ctx context.Context, in *StartContainerRequest, opts ...grpc.CallOption) (*StartContainerResponse, error)
        // StopContainer stops a running container with a grace period (i.e., timeout).
        // This call is idempotent, and must not return an error if the container has
        // already been stopped.
        // TODO: what must the runtime do after the grace period is reached?
        StopContainer(ctx context.Context, in *StopContainerRequest, opts ...grpc.CallOption) (*StopContainerResponse, error)
        // RemoveContainer removes the container. If the container is running, the
        // container must be forcibly removed.
        // This call is idempotent, and must not return an error if the container has
        // already been removed.
        RemoveContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*RemoveContainerResponse, error)
        // ListContainers lists all containers by filters.
        ListContainers(ctx context.Context, in *ListContainersRequest, opts ...grpc.CallOption) (*ListContainersResponse, error)
        // ContainerStatus returns status of the container. If the container is not
        // present, returns an error.
        ContainerStatus(ctx context.Context, in *ContainerStatusRequest, opts ...grpc.CallOption) (*ContainerStatusResponse, error)
        // UpdateContainerResources updates ContainerConfig of the container.
        UpdateContainerResources(ctx context.Context, in *UpdateContainerResourcesRequest, opts ...grpc.CallOption) (*UpdateContainerResourcesResponse, error)
        // ReopenContainerLog asks runtime to reopen the stdout/stderr log file
        // for the container. This is often called after the log file has been
        // rotated. If the container is not running, container runtime can choose
        // to either create a new log file and return nil, or return an error.
        // Once it returns error, new container log file MUST NOT be created.
        ReopenContainerLog(ctx context.Context, in *ReopenContainerLogRequest, opts ...grpc.CallOption) (*ReopenContainerLogResponse, error)
        // ExecSync runs a command in a container synchronously.
        ExecSync(ctx context.Context, in *ExecSyncRequest, opts ...grpc.CallOption) (*ExecSyncResponse, error)
        // Exec prepares a streaming endpoint to execute a command in the container.
        Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error)
        // Attach prepares a streaming endpoint to attach to a running container.
        Attach(ctx context.Context, in *AttachRequest, opts ...grpc.CallOption) (*AttachResponse, error)
        // PortForward prepares a streaming endpoint to forward ports from a PodSandbox.
        PortForward(ctx context.Context, in *PortForwardRequest, opts ...grpc.CallOption) (*PortForwardResponse, error)
        // ContainerStats returns stats of the container. If the container does not
        // exist, the call returns an error.
        ContainerStats(ctx context.Context, in *ContainerStatsRequest, opts ...grpc.CallOption) (*ContainerStatsResponse, error)
        // ListContainerStats returns stats of all running containers.
        ListContainerStats(ctx context.Context, in *ListContainerStatsRequest, opts ...grpc.CallOption) (*ListContainerStatsResponse, error)
        // UpdateRuntimeConfig updates the runtime configuration based on the given request.
        UpdateRuntimeConfig(ctx context.Context, in *UpdateRuntimeConfigRequest, opts ...grpc.CallOption) (*UpdateRuntimeConfigResponse, error)
        // Status returns the status of the runtime.
        Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
}

(cri-api/pkg/apis/runtime/v1alpha2/api.pb.go)
๋‹จ์ง€ gRPC๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปจํ…Œ์ด๋„ˆ ๋Ÿฐํƒ€์ž„ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋ฟ์ž…๋‹ˆ๋‹ค.

type runtimeServiceClient struct {
        cc *grpc.ClientConn
}

(cri-api/pkg/apis/runtime/v1alpha2/api.pb.go)

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

(cri-api/pkg/apis/runtime/v1alpha2/api.pb.go)

Container Runtime์ด ๊ตฌํ˜„์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. RuntimeServiceServer:

kubernetes/kubernetes์˜ cri-api์˜ ๊ธด ๋ชฉ๋ก

// RuntimeServiceServer is the server API for RuntimeService service.
type RuntimeServiceServer interface {
        // Version returns the runtime name, runtime version, and runtime API version.
        Version(context.Context, *VersionRequest) (*VersionResponse, error)
        // RunPodSandbox creates and starts a pod-level sandbox. Runtimes must ensure
        // the sandbox is in the ready state on success.
        RunPodSandbox(context.Context, *RunPodSandboxRequest) (*RunPodSandboxResponse, error)
        // StopPodSandbox stops any running process that is part of the sandbox and
        // reclaims network resources (e.g., IP addresses) allocated to the sandbox.
        // If there are any running containers in the sandbox, they must be forcibly
        // terminated.
        // This call is idempotent, and must not return an error if all relevant
        // resources have already been reclaimed. kubelet will call StopPodSandbox
        // at least once before calling RemovePodSandbox. It will also attempt to
        // reclaim resources eagerly, as soon as a sandbox is not needed. Hence,
        // multiple StopPodSandbox calls are expected.
        StopPodSandbox(context.Context, *StopPodSandboxRequest) (*StopPodSandboxResponse, error)
        // RemovePodSandbox removes the sandbox. If there are any running containers
        // in the sandbox, they must be forcibly terminated and removed.
        // This call is idempotent, and must not return an error if the sandbox has
        // already been removed.
        RemovePodSandbox(context.Context, *RemovePodSandboxRequest) (*RemovePodSandboxResponse, error)
        // PodSandboxStatus returns the status of the PodSandbox. If the PodSandbox is not
        // present, returns an error.
        PodSandboxStatus(context.Context, *PodSandboxStatusRequest) (*PodSandboxStatusResponse, error)
        // ListPodSandbox returns a list of PodSandboxes.
        ListPodSandbox(context.Context, *ListPodSandboxRequest) (*ListPodSandboxResponse, error)
        // CreateContainer creates a new container in specified PodSandbox
        CreateContainer(context.Context, *CreateContainerRequest) (*CreateContainerResponse, error)
        // StartContainer starts the container.
        StartContainer(context.Context, *StartContainerRequest) (*StartContainerResponse, error)
        // StopContainer stops a running container with a grace period (i.e., timeout).
        // This call is idempotent, and must not return an error if the container has
        // already been stopped.
        // TODO: what must the runtime do after the grace period is reached?
        StopContainer(context.Context, *StopContainerRequest) (*StopContainerResponse, error)
        // RemoveContainer removes the container. If the container is running, the
        // container must be forcibly removed.
        // This call is idempotent, and must not return an error if the container has
        // already been removed.
        RemoveContainer(context.Context, *RemoveContainerRequest) (*RemoveContainerResponse, error)
        // ListContainers lists all containers by filters.
        ListContainers(context.Context, *ListContainersRequest) (*ListContainersResponse, error)
        // ContainerStatus returns status of the container. If the container is not
        // present, returns an error.
        ContainerStatus(context.Context, *ContainerStatusRequest) (*ContainerStatusResponse, error)
        // UpdateContainerResources updates ContainerConfig of the container.
        UpdateContainerResources(context.Context, *UpdateContainerResourcesRequest) (*UpdateContainerResourcesResponse, error)
        // ReopenContainerLog asks runtime to reopen the stdout/stderr log file
        // for the container. This is often called after the log file has been
        // rotated. If the container is not running, container runtime can choose
        // to either create a new log file and return nil, or return an error.
        // Once it returns error, new container log file MUST NOT be created.
        ReopenContainerLog(context.Context, *ReopenContainerLogRequest) (*ReopenContainerLogResponse, error)
        // ExecSync runs a command in a container synchronously.
        ExecSync(context.Context, *ExecSyncRequest) (*ExecSyncResponse, error)
        // Exec prepares a streaming endpoint to execute a command in the container.
        Exec(context.Context, *ExecRequest) (*ExecResponse, error)
        // Attach prepares a streaming endpoint to attach to a running container.
        Attach(context.Context, *AttachRequest) (*AttachResponse, error)
        // PortForward prepares a streaming endpoint to forward ports from a PodSandbox.
        PortForward(context.Context, *PortForwardRequest) (*PortForwardResponse, error)
        // ContainerStats returns stats of the container. If the container does not
        // exist, the call returns an error.
        ContainerStats(context.Context, *ContainerStatsRequest) (*ContainerStatsResponse, error)
        // ListContainerStats returns stats of all running containers.
        ListContainerStats(context.Context, *ListContainerStatsRequest) (*ListContainerStatsResponse, error)
        // UpdateRuntimeConfig updates the runtime configuration based on the given request.
        UpdateRuntimeConfig(context.Context, *UpdateRuntimeConfigRequest) (*UpdateRuntimeConfigResponse, error)
        // Status returns the status of the runtime.
        Status(context.Context, *StatusRequest) (*StatusResponse, error)
}

(cri-api/pkg/apis/runtime/v1alpha2/api.pb.go)
kubectl exec๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋‚˜์š”?

๊ทธ๋ ‡๋‹ค๋ฉด kubelet๊ณผ ์ปจํ…Œ์ด๋„ˆ ๋Ÿฐํƒ€์ž„ ์‚ฌ์ด์˜ ์—ฐ๊ฒฐ์„ ํ™•์ธํ•ด์•ผ๊ฒ ์ฃ ? ์ ๊ฒ€ ํ•ด๋ณด์ž.

exec ๋ช…๋ น ์ „ํ›„์— ์ด ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๊ณ  ์ฐจ์ด์ ์„ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. ์ œ ๊ฒฝ์šฐ ์ฐจ์ด์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

// worker node
$ ss -a -p |grep kubelet
...
u_str  ESTAB      0      0       * 157937                * 157387                users:(("kubelet",pid=5714,fd=33))
...

ํ โ€ฆ kubelet(pid=5714)๊ณผ ์•Œ๋ ค์ง€์ง€ ์•Š์€ ๊ฒƒ ์‚ฌ์ด์˜ ์ƒˆ๋กœ์šด ์œ ๋‹‰์Šค ์†Œ์ผ“ ์—ฐ๊ฒฐ์ž…๋‹ˆ๋‹ค. ๋ญ๊ฐ€ ๋ ์ˆ˜ ์žˆ์—ˆ๋Š”์ง€? ๋งž์Šต๋‹ˆ๋‹ค. Docker(pid=1186)์ž…๋‹ˆ๋‹ค!

// worker node
$ ss -a -p |grep 157387
...
u_str  ESTAB      0      0       * 157937                * 157387                users:(("kubelet",pid=5714,fd=33))
u_str  ESTAB      0      0      /var/run/docker.sock 157387                * 157937                users:(("dockerd",pid=1186,fd=14))
...

๊ธฐ์–ตํ•˜์‹œ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, ๋‹ค์Œ์€ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋Š” docker ๋ฐ๋ชฌ ํ”„๋กœ์„ธ์Šค(pid=1186)์ž…๋‹ˆ๋‹ค.

// worker node
$ ps -afx
...
 1186 ?        Ssl    0:55 /usr/bin/dockerd -H fd://
17784 ?        Sl     0:00      _ docker-containerd-shim 53a0a08547b2f95986402d7f3b3e78702516244df049ba6c5aa012e81264aa3c /var/run/docker/libcontainerd/53a0a08547b2f95986402d7f3
17801 pts/2    Ss     0:00          _ sh
17827 pts/2    S+     0:00              _ sleep 5000
...

4. ์ปจํ…Œ์ด๋„ˆ ๋Ÿฐํƒ€์ž„์—์„œ์˜ ํ™œ๋™

๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ๋Š”์ง€ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด CRI-O ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Docker์—์„œ๋Š” ๋…ผ๋ฆฌ๊ฐ€ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„์„ ๋‹ด๋‹นํ•˜๋Š” ์„œ๋ฒ„๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. RuntimeServiceServer:

// Server implements the RuntimeService and ImageService
type Server struct {
        config          libconfig.Config
        seccompProfile  *seccomp.Seccomp
        stream          StreamService
        netPlugin       ocicni.CNIPlugin
        hostportManager hostport.HostPortManager

        appArmorProfile string
        hostIP          string
        bindAddress     string

        *lib.ContainerServer
        monitorsChan      chan struct{}
        defaultIDMappings *idtools.IDMappings
        systemContext     *types.SystemContext // Never nil

        updateLock sync.RWMutex

        seccompEnabled  bool
        appArmorEnabled bool
}

(cri-o/server/server.go)

// Exec prepares a streaming endpoint to execute a command in the container.
func (s *Server) Exec(ctx context.Context, req *pb.ExecRequest) (resp *pb.ExecResponse, err error) {
        const operation = "exec"
        defer func() {
                recordOperation(operation, time.Now())
                recordError(operation, err)
        }()

        resp, err = s.getExec(req)
        if err != nil {
                return nil, fmt.Errorf("unable to prepare exec endpoint: %v", err)
        }

        return resp, nil
}

(cri-o/์„œ๋ฒ„/container_exec.go)

์ฒด์ธ ๋์—์„œ ์ปจํ…Œ์ด๋„ˆ ๋Ÿฐํƒ€์ž„์€ ์ž‘์—…์ž ๋…ธ๋“œ์—์„œ ๋‹ค์Œ ๋ช…๋ น์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

// ExecContainer prepares a streaming endpoint to execute a command in the container.
func (r *runtimeOCI) ExecContainer(c *Container, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
        processFile, err := prepareProcessExec(c, cmd, tty)
        if err != nil {
                return err
        }
        defer os.RemoveAll(processFile.Name())

        args := []string{rootFlag, r.root, "exec"}
        args = append(args, "--process", processFile.Name(), c.ID())
        execCmd := exec.Command(r.path, args...)
        if v, found := os.LookupEnv("XDG_RUNTIME_DIR"); found {
                execCmd.Env = append(execCmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", v))
        }
        var cmdErr, copyError error
        if tty {
                cmdErr = ttyCmd(execCmd, stdin, stdout, resize)
        } else {
                if stdin != nil {
                        // Use an os.Pipe here as it returns true *os.File objects.
                        // This way, if you run 'kubectl exec <pod> -i bash' (no tty) and type 'exit',
                        // the call below to execCmd.Run() can unblock because its Stdin is the read half
                        // of the pipe.
                        r, w, err := os.Pipe()
                        if err != nil {
                                return err
                        }
                        go func() { _, copyError = pools.Copy(w, stdin) }()

                        execCmd.Stdin = r
                }
                if stdout != nil {
                        execCmd.Stdout = stdout
                }
                if stderr != nil {
                        execCmd.Stderr = stderr
                }

                cmdErr = execCmd.Run()
        }

        if copyError != nil {
                return copyError
        }
        if exitErr, ok := cmdErr.(*exec.ExitError); ok {
                return &utilexec.ExitErrorWrapper{ExitError: exitErr}
        }
        return cmdErr
}

(cri-o/internal/oci/runtime_oci.go)

kubectl exec๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋‚˜์š”?

๋งˆ์ง€๋ง‰์œผ๋กœ ์ปค๋„์€ ๋‹ค์Œ ๋ช…๋ น์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

kubectl exec๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋‚˜์š”?

๋ฏธ๋ฆฌ ์•Œ๋ฆผ

  • API ์„œ๋ฒ„๋Š” kubelet์— ๋Œ€ํ•œ ์—ฐ๊ฒฐ์„ ์ดˆ๊ธฐํ™”ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋‹ค์Œ ์—ฐ๊ฒฐ์€ ๋Œ€ํ™”ํ˜• ์‹คํ–‰ ์„ธ์…˜์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ์ง€์†๋ฉ๋‹ˆ๋‹ค.
    • kubectl๊ณผ api-server ์‚ฌ์ด;
    • api-server์™€ kubectl ์‚ฌ์ด;
    • kubelet๊ณผ ์ปจํ…Œ์ด๋„ˆ ๋Ÿฐํƒ€์ž„ ์‚ฌ์ด.
  • Kubectl ๋˜๋Š” api-server๋Š” ์ž‘์—…์ž ๋…ธ๋“œ์—์„œ ์•„๋ฌด๊ฒƒ๋„ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. kubelet์€ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์ง€๋งŒ ์ด๋Ÿฌํ•œ ์ž‘์—…์„ ์œ„ํ•ด ์ปจํ…Œ์ด๋„ˆ์˜ ๋Ÿฐํƒ€์ž„๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

๋ฆฌ์†Œ์Šค

๋ฒˆ์—ญ๊ฐ€์˜ ์ถ”์‹ 

๋ธ”๋กœ๊ทธ์—์„œ๋„ ์ฝ์–ด๋ณด์„ธ์š”.

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€