Նշում. թարգմ.Հոդվածի հեղինակը՝ Էրքան Էրոլը, ինժեներ SAP-ից, կիսվում է թիմի գործունեության մեխանիզմների իր ուսումնասիրությամբ։ kubectl exec, այնքան ծանոթ բոլորին, ովքեր աշխատում են Kubernetes-ի հետ: Նա ամբողջ ալգորիթմը ուղեկցում է Kubernetes-ի սկզբնական կոդի (և հարակից նախագծերի) ցուցակներով, որոնք թույլ են տալիս հասկանալ թեման այնքան խորը, որքան պահանջվում է:
Մի ուրբաթ, գործընկերներից մեկը մոտեցավ ինձ և հարցրեց, թե ինչպես կատարել հրամանը պատիճ օգտագործելով հաճախորդ-գնա. Ես չկարողացա պատասխանել նրան և հանկարծ հասկացա, որ ոչինչ չգիտեմ գործողության մեխանիզմի մասին kubectl exec. Այո, ես որոշակի պատկերացումներ ունեի դրա կառուցվածքի մասին, բայց ես 100%-ով վստահ չէի դրանց ճիշտության մեջ և, հետևաբար, որոշեցի զբաղվել այս հարցով: Ուսումնասիրելով բլոգները, փաստաթղթերը և սկզբնական կոդը՝ ես շատ նոր բաներ սովորեցի, և այս հոդվածում ուզում եմ կիսվել իմ բացահայտումներով և հասկացողությամբ: Եթե ինչ-որ բան սխալ է, խնդրում եմ կապվեք ինձ հետ Twitter.
Ուսուցում
MacBook-ում կլաստեր ստեղծելու համար ես կլոնավորեցի ecomm-integration-ballerina/kubernetes-cluster. Այնուհետև ես ուղղեցի kubelet կոնֆիգուրայի հանգույցների IP հասցեները, քանի որ լռելյայն կարգավորումները թույլ չէին տալիս kubectl exec. Դուք կարող եք ավելին կարդալ դրա հիմնական պատճառի մասին այստեղ.
Ցանկացած մեքենա = իմ MacBook-ը
Հիմնական հանգույցի IP = 192.168.205.10
Աշխատող հանգույց IP = 192.168.205.11
API սերվերի պորտ = 6443
Բաղադրիչներ
kubectl exec գործընթացըԵրբ մենք կատարում ենք «kubectl exec...» գործընթացը սկսվում է: Դա կարելի է անել ցանկացած մեքենայի վրա, որը հասանելի է K8s API սերվերին: Նշում Թարգմանություն. Վահանակի ցուցակներում հետագայում հեղինակը օգտագործում է «ցանկացած մեքենա» մեկնաբանությունը, ինչը ենթադրում է, որ հետագա հրամանները կարող են իրականացվել ցանկացած նման սարքի վրա, որը հասանելի է Kubernetes-ին:
api սերվերԲաղադրիչ հիմնական հանգույցի վրա, որն ապահովում է մուտք դեպի Kubernetes API: Սա Կուբերնետեսի կառավարման ինքնաթիռի ճակատն է:
կուբելետԳործակալ, որն աշխատում է կլաստերի յուրաքանչյուր հանգույցի վրա: Այն ապահովում է բեռնարկղերի աշխատանքը պատիճում:
կոնտեյների գործարկման ժամանակը (կոնտեյների գործարկման ժամանակ). Ծրագիրը, որը պատասխանատու է բեռնարկղերի գործարկման համար: Օրինակներ՝ Docker, CRI-O, կոնտեյներ…
միջուկOS միջուկը աշխատող հանգույցի վրա; պատասխանատու է գործընթացի կառավարման համար:
թիրախ (թիրախ) ամանկոնտեյներ, որը պատիճի մաս է և աշխատում է աշխատող հանգույցներից մեկի վրա:
Այն, ինչ ես հայտնաբերեցի
1. Հաճախորդի կողմից ակտիվություն
Ստեղծեք պատիճ անունների տարածքում 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).
Մենք կարող ենք նաև դիտարկել հարցումը 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 թույլ է տալիս մուլտիպլեքսացնել առանձին stdin/stdout/stderr/spdy-error «հոսքերը» մեկ TCP կապի միջոցով:
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
}
Պահանջվող գործողությունները կատարելու համար 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")
}
Իհարկե, վերջնակետի մասին տվյալները վերցված են հանգույցի մասին տեղեկություններից.
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)
Ուռա՜ Կուբելեթն այժմ ունի նավահանգիստ (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-ի 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
}
Տեսնենք, թե ինչ է տեղի ունենում հիմնական հանգույցի վրա:
Նախ, մենք պարզում ենք աշխատող հանգույցի 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, այն կվերանա, այնպես որ ես գիտեմ, որ կապը հաստատվել է api-սերվերի կողմից exec հրամանի կատարման արդյունքում։
Բայց սպասեք. ինչպե՞ս է դա հաջողվել Կուբելեթին: Kubelet-ն ունի deemon, որն ապահովում է մուտք դեպի 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-ն իրականացնում է ինտերֆեյսը RuntimeServiceClient, որը Container Runtime Interface-ի մի մասն է (դրա մասին ավելին գրել ենք, օրինակ. այստեղ - մոտ. թարգմ.):
Երկար ցուցակ cri-api-ից kubernetes/kubernetes-ում
// 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)
}
Container Runtime-ը պատասխանատու է իրականացման համար RuntimeServiceServer:
Երկար ցուցակ cri-api-ից kubernetes/kubernetes-ում
// 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)
}
API սերվերը կարող է նաև սկզբնավորել կապը kubelet-ին:
Հետևյալ կապերը պահպանվում են մինչև ինտերակտիվ exec նիստի ավարտը.
kubectl-ի և api-սերվերի միջև;
api-server-ի և kubectl-ի միջև;
kubelet-ի և կոնտեյների գործարկման ժամանակի միջև:
Kubectl-ը կամ api-server-ը չեն կարող որևէ բան գործարկել աշխատանքային հանգույցների վրա: Kubelet-ը կարող է աշխատել, բայց այն նաև փոխազդում է կոնտեյների գործարկման ժամանակի հետ՝ այդ բաներն անելու համար: