для обычных иKubernetes
Для инженеров YAML, которые имеют дело с этим, наиболее часто используемые команды:kubectl exec
, который позволяет отлаживать приложение, выполняя команды непосредственно внутри контейнера. Если вы не удовлетворены его использованием, хотите знатьkubectl exec
работает, то эта статья заслуживает вашего внимательного прочтения. Эта статья будет относиться кkubectl
,API Server
,Kubelet
и соответствующий код в API-интерфейсе Container Runtime Interface (CRI) Docker, чтобы понять, как работает эта команда.
Принцип работы kubectl exec можно представить на схеме:
Сначала рассмотрим пример:
🐳 → kubectl version --short
Client Version: v1.15.0
Server Version: v1.15.3
🐳 → kubectl run nginx --image=nginx --port=80 --generator=run-pod/v1
pod/nginx created
🐳 → kubectl get po
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 6s
🐳 → kubectl exec nginx -- date
Sat Jan 25 18:47:52 UTC 2020
🐳 → kubectl exec -it nginx -- /bin/bash
root@nginx:/#
Первый kubectl Exec выполняется внутри контейнераdate
команда, второй kubectl exec использует-i
и-t
Аргумент переходит в интерактивную оболочку контейнера.
Повторите вторую команду kubectl exec, чтобы распечатать более подробный журнал:
🐳 → kubectl -v=7 exec -it nginx -- /bin/bash
I0125 10:51:55.434043 28053 loader.go:359] Config loaded from file: /home/isim/.kube/kind-config-linkerd
I0125 10:51:55.438595 28053 round_trippers.go:416] GET https://127.0.0.1:38545/api/v1/namespaces/default/pods/nginx
I0125 10:51:55.438607 28053 round_trippers.go:423] Request Headers:
I0125 10:51:55.438611 28053 round_trippers.go:426] Accept: application/json, */*
I0125 10:51:55.438615 28053 round_trippers.go:426] User-Agent: kubectl/v1.15.0 (linux/amd64) kubernetes/e8462b5
I0125 10:51:55.445942 28053 round_trippers.go:441] Response Status: 200 OK in 7 milliseconds
I0125 10:51:55.451050 28053 round_trippers.go:416] POST https://127.0.0.1:38545/api/v1/namespaces/default/pods/nginx/exec?command=%2Fbin%2Fbash&container=nginx&stdin=true&stdout=true&tty=true
I0125 10:51:55.451063 28053 round_trippers.go:423] Request Headers:
I0125 10:51:55.451067 28053 round_trippers.go:426] X-Stream-Protocol-Version: v4.channel.k8s.io
I0125 10:51:55.451090 28053 round_trippers.go:426] X-Stream-Protocol-Version: v3.channel.k8s.io
I0125 10:51:55.451096 28053 round_trippers.go:426] X-Stream-Protocol-Version: v2.channel.k8s.io
I0125 10:51:55.451100 28053 round_trippers.go:426] X-Stream-Protocol-Version: channel.k8s.ioI0125 10:51:55.451121 28053 round_trippers.go:426] User-Agent: kubectl/v1.15.0 (linux/amd64) kubernetes/e8462b5
I0125 10:51:55.465690 28053 round_trippers.go:441] Response Status: 101 Switching Protocols in 14 milliseconds
root@nginx:/#
Здесь есть два важных HTTP-запроса:
-
GET
запрос на использованиеПолучить информацию о модуле. - Запрос POST вызывает подресурс пода
exec
Выполнение команд внутри контейнера.
Подресурс принадлежит ресурсу K8S и представлен в виде подпути под родительским ресурсом, например
/logs
,/status
,/scale
,/exec
Ждать. Операции, поддерживаемые каждым подресурсом, варьируются от объекта к объекту.
Наконец сервер API вернулся101 Ugrade
Ответ, указывающий клиенту, что он переключился наSPDY
протокол.
SPDY позволяет мультиплексировать независимые потоки stdin/stdout/stderr/spdy-error в одном TCP-соединении.
1. Анализ исходного кода API-сервера
Запрос сначала пойдет на сервер API, давайте посмотрим, как регистрируется сервер API.rest.ExecRest
Обработчик для обработки запросов подресурсов/exec
из. Этот процессор используется для определенияexec
Узел для входа.
API Server во время запускаПервое, что нужно сделать, это направить встроенныйGenericAPIServer
Загрузите ранний устаревший API (устаревший API):
if c.ExtraConfig.APIResourceConfigSource.VersionEnabled(apiv1.SchemeGroupVersion) {
// ...
if err := m.InstallLegacyAPI(&c, c.GenericConfig.RESTOptionsGetter, legacyRESTStorageProvider); err != nil {
return nil, err
}
}
Во время загрузки API типLegacyRESTStorage
создавать экземпляр,Создаватьstorage.PodStorage
Пример:
podStorage, err := podstore.NewStorage(
restOptionsGetter,
nodeStorage.KubeletConnectionInfo,
c.ProxyTransport,
podDisruptionClient,
)
if err != nil {
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err
}
впоследствииstoreage.PodStorage
Экземпляр будет добавлен на картуrestStorageMap
середина. Обратите внимание, что карта будетpods/exec
сопоставляется сpodStorage
изrest.ExecRest
процессор.
restStorageMap := map[string]rest.Storage{
"pods": podStorage.Pod,
"pods/attach": podStorage.Attach,
"pods/status": podStorage.Status,
"pods/log": podStorage.Log,
"pods/exec": podStorage.Exec,
"pods/portforward": podStorage.PortForward,
"pods/proxy": podStorage.Proxy,
"pods/binding": podStorage.Binding,
"bindings": podStorage.LegacyBinding,
podstorage
Предусмотрено для модулей и подресурсовCURD
Абстракция логики и стратегии. Подробнее см. во встроенномgenericregistry.Store
map restStorageMap
будет экземплярapiGroupInfo
частьGenericAPIServer
середина:
if err := s.installAPIResources(apiPrefix, apiGroupInfo, openAPIModels); err != nil {
return err
}
// Install the version handler.
// Add a handler at /<apiPrefix> to enumerate the supported api versions.
s.Handler.GoRestfulContainer.Add(discovery.NewLegacyRootAPIHandler(s.discoveryAddresses, s.Serializer, apiPrefix).WebService())
вGoRestfulContainer.ServeMuxСопоставит URL-адреса входящего запроса с разными обработчиками.
Далее ориентируемся на процессорtherest.ExecRest
работает, этоConnect()
метод вызывает функциюpod.ExecLocation()определить размер контейнера в стручкеexec
подресурсURL
:
// 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
}
функцияpod.ExecLocation()
возвращениеURLИспользуется сервером API, чтобы решить, к какому узлу подключиться.
Далее проанализируйте узлы наKubelet
исходный код.
2. Анализ исходного кода Kubelet
прибытьKubelet
Здесь нам нужно обратить внимание на две вещи:
- Как регистрируется Kubelet
exec
процессор? - Кубелет с
Docker API
Как взаимодействовать?
Процесс инициализации KubeletОчень сложный, в основном включающий две функции:
-
PreInitRuntimeService(): использовать
dockershim
пакет для инициализацииCRI
. - RunKubelet(): Зарегистрируйте обработчик и запустите службу Kubelet.
обработчик регистрации
Когда Kubelet запускается, егоRunKubelet()функция вызовет приватную функциюstartKubelet()
Приходитьзапускатьkubelet.Kubelet
примеризListenAndServe()
метод, то метод будетфункция вызоваListenAndServeKubeletServer()
, используя конструкторNewServer()
Чтобы установить «отладочный» процессор:
// NewServer initializes and configures a kubelet.Server object to handle HTTP requests.
func NewServer(
// ...
criHandler http.Handler) Server {
// ...
if enableDebuggingHandlers {
server.InstallDebuggingHandlers(criHandler)
if enableContentionProfiling {
goruntime.SetBlockProfileRate(1)
}
} else {
server.InstallDebuggingDisabledHandlers()
}
return server
}
InstallDebuggingHandlers()
использование функцииgetExec()
Обработчик для регистрации схемы HTTP-запроса:
// InstallDebuggingHandlers registers the HTTP request patterns that serve logs or run commands/containers
func (s *Server) InstallDebuggingHandlers(criHandler http.Handler) {
// ...
ws = new(restful.WebService)
ws.
Path("/exec")
ws.Route(ws.GET("/{podNamespace}/{podID}/{containerName}").
To(s.getExec).
Operation("getExec"))
ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}").
To(s.getExec).
Operation("getExec"))
ws.Route(ws.GET("/{podNamespace}/{podID}/{uid}/{containerName}").
To(s.getExec).
Operation("getExec"))
ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}/{containerName}").
To(s.getExec).
Operation("getExec"))
s.restfulCont.Add(ws)
вgetExec()
Процессор снова вызоветs.host
в случаеGetExec()
метод:
// getExec handles requests to run a command inside a container.
func (s *Server) getExec(request *restful.Request, response *restful.Response) {
// ...
podFullName := kubecontainer.GetPodFullName(pod)
url, err := s.host.GetExec(podFullName, params.podUID, params.containerName, params.cmd, *streamOpts)
if err != nil {
streaming.WriteError(err, response.ResponseWriter)
return
}
// ...
}
s.host
создается какkubelet.Kubelet
экземпляр типа, который содержит ссылки наStreamingRuntime
интерфейс, интерфейс в свою очередьсоздавать экземплярзаkubeGenericRuntimeManager
экземпляр, то естьменеджер среды выполнения. Менеджер среды выполнения — это Kubelet сDocker API
ключевые компоненты взаимодействия,GetExec()
Метод реализуется им:
// GetExec gets the endpoint the runtime will serve the exec request from.
func (m *kubeGenericRuntimeManager) GetExec(id kubecontainer.ContainerID, cmd []string, stdin, stdout, stderr, tty bool) (*url.URL, error) {
// ...
resp, err := m.runtimeService.Exec(req)
if err != nil {
return nil, err
}
return url.Parse(resp.Url)
}
GetExec()
позвоню сноваruntimeService.Exec()
способ, копай дальше и найдешьruntimeService
определяется в пакете CRIинтерфейс.kuberuntime.kubeGenericRuntimeManager
изruntimeService
создается какkuberuntime.instrumentedRuntimeService
тип, реализуемый имruntimeService.Exec()
метод:
func (in instrumentedRuntimeService) Exec(req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
const operation = "exec"
defer recordOperation(operation, time.Now())
resp, err := in.service.Exec(req)
recordError(operation, err)
return resp, err
}
Вложенные сервисные объекты экземпляров toolsedRuntimeServiceсоздавать экземплярзаtheremote.RemoteRuntimeService
экземпляр типа. Этот тип реализуетExec()
метод:
// Exec prepares a streaming endpoint to execute a command in the container, and returns the address.
func (r *RemoteRuntimeService) Exec(req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
ctx, cancel := getContextWithTimeout(r.timeout)
defer cancel()
resp, err := r.runtimeClient.Exec(ctx, req)
if err != nil {
klog.Errorf("Exec %s '%s' from runtime service failed: %v", req.ContainerId, strings.Join(req.Cmd, " "), err)
return nil, err
}
if resp.Url == "" {
errorMessage := "URL is not set"
klog.Errorf("Exec failed: %s", errorMessage)
return nil, errors.New(errorMessage)
}
return resp, nil
}
Exec()
метод будет/runtime.v1alpha2.RuntimeService/Exec
инициироватьgRPC
перечислитьчтобы сторона выполнения подготовила конечную точку потоковой передачи для выполнения команд в контейнере (см.Docker shim
См. следующий раздел для получения дополнительной информации о настройке его в качестве сервера gRPC).
сервер gRPC, позвонивRuntimeServiceServer.Exec()
путь кобработать запрос, метод состоит изdockershim.dockerService
Реализация структуры:
// Exec prepares a streaming endpoint to execute a command in the container, and returns the address.
func (ds *dockerService) Exec(_ context.Context, req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
if ds.streamingServer == nil {
return nil, streaming.NewErrorStreamingDisabled("exec")
}
_, err := checkContainerStatus(ds.client, req.ContainerId)
if err != nil {
return nil, err
}
return ds.streamingServer.GetExec(req)
}
строка 10ThestreamingServer
Являетсяstreaming.Serverинтерфейс, который находится в конструктореdockershim.NewDockerService()
создается в:
// create streaming server if configured.
if streamingConfig != nil {
var err error
ds.streamingServer, err = streaming.NewServer(*streamingConfig, ds.streamingRuntime)
if err != nil {
return nil, err
}
}
посмотриGetExec()
Как реализован метод:
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
}
Видно, что это всего лишь URL-адрес простой комбинации токенов, возвращаемый клиенту Причина, по которой генерируется токен, заключается в том, что команда пользователя может содержать различные символы и символы различной длины, которые необходимо отформатировать как простой токен. Токен будет кэшироваться локально, и последующий реальный запрос exec будет нести этот токен, и предыдущий конкретный запрос будет найден через этот токен. вrestful.WebService
экземпляр будет подexec
Запросы направляются на эту конечную точку:
// InstallDebuggingHandlers registers the HTTP request patterns that serve logs or run commands/containers
func (s *Server) InstallDebuggingHandlers(criHandler http.Handler) {
// ...
ws = new(restful.WebService)
ws.
Path("/exec")
ws.Route(ws.GET("/{podNamespace}/{podID}/{containerName}").
To(s.getExec).
Operation("getExec"))
ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}").
To(s.getExec).
Operation("getExec"))
ws.Route(ws.GET("/{podNamespace}/{podID}/{uid}/{containerName}").
To(s.getExec).
Operation("getExec"))
ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}/{containerName}").
To(s.getExec).
Operation("getExec"))
s.restfulCont.Add(ws)
Создайте прокладку Docker
PreInitRuntimeService()
функцияв качестве сервера gRPC,ответственныйСоздайте и начнитеДокер шим. в волеdockershim.dockerService
Когда создается экземпляр типа, пусть он вложенstreamingRuntime
ссылка на экземплярdockershim.NativeExecHandler
экземпляр (который реализуетdockershim.ExecHandlerинтерфейс).
ds := &dockerService{
// ...
streamingRuntime: &streamingRuntime{
client: client,
execHandler: &NativeExecHandler{},
},
// ...
}
с помощью Докераexec
Основная реализация API для выполнения команд в контейнереNativeExecHandler.ExecInContainer()
метод:
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 {
// ...
startOpts := dockertypes.ExecStartCheck{Detach: false, Tty: tty}
streamOpts := libdocker.StreamOptions{
InputStream: stdin,
OutputStream: stdout,
ErrorStream: stderr,
RawTerminal: tty,
ExecStarted: execStarted,
}
err = client.StartExec(execObj.ID, startOpts, streamOpts)
if err != nil {
return err
}
// ...
вот и конецKubelet
Вызвать Докерexec
Место API.
Последнее, что нужно знать, это то, чтоstreamingServer
как работает процессорexec
просить. Сначала нужно его найтиexec
обработчик, мы прямо из конструктораstreaming.NewServer()
Начните смотреть вниз, потому что это/exec/{token}
путь привязан кserveExec
Где находится процессор:
ws := &restful.WebService{}
endpoints := []struct {
path string
handler restful.RouteFunction
}{
{"/exec/{token}", s.serveExec},
{"/attach/{token}", s.serveAttach},
{"/portforward/{token}", s.servePortForward},
}
все отправлено вdockershim.dockerService
Запросы экземпляров заканчиваются вstreamingServer
сделано на процессоре, потому чтоdockerService.ServeHTTP()метод вызоветstreamingServer
примерServeHTTP()
метод.
serveExec
процессор будетВызов функции remoteCommand.ServeExec(), что делает эта функция? он вызовет ранее упомянутыйExecutor.ExecInContainer()
метод, в то время какExecInContainer()
Путь в том, чтобы знать, как взаимодействовать с Dockerexec
Для связи через API:
// ServeExec handles requests to execute a command in a container. After
// creating/receiving the required streams, it delegates the actual execution
// to the executor.
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) {
// ...
err := executor.ExecInContainer(podName, uid, container, cmd, ctx.stdinStream, ctx.stdoutStream, ctx.stderrStream, ctx.tty, ctx.resizeChan, 0)
if err != nil {
// ...
} else {
// ...
}
}
3. Резюме
Эта статья через толкованиеkubectl
,API Server
иCRI
исходный код, чтобы помочь вам понятьkubectl exec
Как работает команда, разумеется, Docker здесь не при чемexec
Детали API, также не охваченныеdocker exec
Принцип работы.
Сначала kubectl отправляет сообщение на сервер API.GET
иPOST
запрос, сервер API возвратил101 Ugrade
Ответ, указывающий клиенту, что он переключился наSPDY
протокол.
Затем API-сервер используетstorage.PodStorage
иrest.ExecRest
для обеспечения отображения процессора и логики выполнения, гдеrest.ExecRest
процессор решаетexec
Узел для входа.
Наконец, КубелетDocker shim
запрашивает URL-адрес конечной точки потоковой передачи и помещаетexec
Перенаправить запрос в Dockerexec
API. Затем kubelet добавляет этот URL вRedirect
Метод возвращается на сервер API, и запрос будет перенаправлен на соответствующий сервер потоковой передачи.exec
запрос и поддерживать длинную цепочку.
Хотя в этой статье рассматривается только команда kubectl exec, другие подкоманды, такие какattach
,port-forward
,log
и т. д.) следует аналогичному шаблону реализации:
Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12 Адрес выпуска пакета автономной установкиstore.lameleg.com, добро пожаловать на опыт. Использовалась последняя версия sealos v3.3.6. Конфигурация разрешения имени хоста была оптимизирована, lvscare монтирует /lib/module для решения проблемы загрузки ipvs при загрузке, исправляет несовместимость между netlink сообщества lvscare и ядром 3.10, а sealos генерирует сертификат вековой давности и другие функции. Больше возможностейGitHub.com/Под гневом/Цвет ах.... Добро пожаловать, чтобы отсканировать QR-код ниже, чтобы присоединиться к группе DingTalk.Группа DingTalk объединяет роботов тюленей и может видеть динамику тюленей в режиме реального времени.