2014-06-06 23:40:48 +00:00
|
|
|
/*
|
2016-06-03 00:25:58 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors.
|
2014-06-06 23:40:48 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
2014-06-08 23:10:29 +00:00
|
|
|
|
2015-12-10 20:14:26 +00:00
|
|
|
package server
|
2014-06-06 23:40:48 +00:00
|
|
|
|
|
|
|
import (
|
2015-03-05 21:30:52 +00:00
|
|
|
"crypto/tls"
|
2014-06-06 23:40:48 +00:00
|
|
|
"fmt"
|
2014-07-14 21:48:51 +00:00
|
|
|
"io"
|
2014-07-15 20:24:41 +00:00
|
|
|
"net"
|
2014-06-06 23:40:48 +00:00
|
|
|
"net/http"
|
2015-06-04 06:51:14 +00:00
|
|
|
"net/http/pprof"
|
2016-11-03 00:42:00 +00:00
|
|
|
"net/url"
|
2016-01-29 03:27:56 +00:00
|
|
|
"reflect"
|
2017-02-28 18:43:08 +00:00
|
|
|
goruntime "runtime"
|
2014-07-15 20:24:41 +00:00
|
|
|
"strconv"
|
2014-07-01 21:05:10 +00:00
|
|
|
"strings"
|
2014-07-15 20:24:41 +00:00
|
|
|
"time"
|
2014-06-06 23:40:48 +00:00
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
restful "github.com/emicklei/go-restful"
|
2015-08-05 22:05:17 +00:00
|
|
|
"github.com/golang/glog"
|
2015-10-16 03:00:28 +00:00
|
|
|
cadvisorapi "github.com/google/cadvisor/info/v1"
|
2017-07-18 05:12:24 +00:00
|
|
|
"github.com/google/cadvisor/metrics"
|
2015-08-05 22:05:17 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2017-07-18 05:12:24 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2015-09-29 03:32:20 +00:00
|
|
|
|
2017-06-22 18:24:23 +00:00
|
|
|
"k8s.io/api/core/v1"
|
2017-01-11 14:09:48 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
"k8s.io/apimachinery/pkg/types"
|
2017-02-15 10:34:49 +00:00
|
|
|
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
2017-01-11 14:09:48 +00:00
|
|
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
2017-01-04 15:39:05 +00:00
|
|
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
|
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
2017-01-17 10:38:25 +00:00
|
|
|
"k8s.io/apiserver/pkg/server/healthz"
|
|
|
|
"k8s.io/apiserver/pkg/server/httplog"
|
2017-01-04 15:39:05 +00:00
|
|
|
"k8s.io/apiserver/pkg/util/flushwriter"
|
2017-04-14 09:33:57 +00:00
|
|
|
"k8s.io/client-go/tools/remotecommand"
|
2017-10-16 11:41:50 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
2017-11-08 22:34:54 +00:00
|
|
|
api "k8s.io/kubernetes/pkg/apis/core"
|
|
|
|
"k8s.io/kubernetes/pkg/apis/core/v1/validation"
|
2015-08-05 22:03:47 +00:00
|
|
|
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
2018-03-19 20:20:40 +00:00
|
|
|
"k8s.io/kubernetes/pkg/kubelet/prober"
|
2015-12-10 20:14:26 +00:00
|
|
|
"k8s.io/kubernetes/pkg/kubelet/server/portforward"
|
2017-02-15 10:34:49 +00:00
|
|
|
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
2016-01-07 01:37:12 +00:00
|
|
|
"k8s.io/kubernetes/pkg/kubelet/server/stats"
|
2016-11-03 00:42:00 +00:00
|
|
|
"k8s.io/kubernetes/pkg/kubelet/server/streaming"
|
2017-07-18 05:12:24 +00:00
|
|
|
kubelettypes "k8s.io/kubernetes/pkg/kubelet/types"
|
2016-02-02 02:09:02 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/configz"
|
2015-09-10 03:46:11 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/limitwriter"
|
2014-06-06 23:40:48 +00:00
|
|
|
)
|
|
|
|
|
2016-10-08 04:44:13 +00:00
|
|
|
const (
|
2017-07-18 05:12:24 +00:00
|
|
|
metricsPath = "/metrics"
|
|
|
|
cadvisorMetricsPath = "/metrics/cadvisor"
|
2018-03-19 20:20:40 +00:00
|
|
|
proberMetricsPath = "/metrics/probes"
|
2017-07-18 05:12:24 +00:00
|
|
|
specPath = "/spec/"
|
|
|
|
statsPath = "/stats/"
|
|
|
|
logsPath = "/logs/"
|
2016-10-08 04:44:13 +00:00
|
|
|
)
|
|
|
|
|
2014-07-15 13:54:23 +00:00
|
|
|
// Server is a http.Handler which exposes kubelet functionality over HTTP.
|
|
|
|
type Server struct {
|
2016-01-14 19:19:26 +00:00
|
|
|
auth AuthInterface
|
|
|
|
host HostInterface
|
|
|
|
restfulCont containerInterface
|
|
|
|
resourceAnalyzer stats.ResourceAnalyzer
|
2016-03-29 00:05:02 +00:00
|
|
|
runtime kubecontainer.Runtime
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
2015-03-05 21:30:52 +00:00
|
|
|
type TLSOptions struct {
|
|
|
|
Config *tls.Config
|
|
|
|
CertFile string
|
|
|
|
KeyFile string
|
|
|
|
}
|
|
|
|
|
2015-09-29 03:32:20 +00:00
|
|
|
// containerInterface defines the restful.Container functions used on the root container
|
|
|
|
type containerInterface interface {
|
|
|
|
Add(service *restful.WebService) *restful.Container
|
|
|
|
Handle(path string, handler http.Handler)
|
|
|
|
Filter(filter restful.FilterFunction)
|
|
|
|
ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|
|
|
RegisteredWebServices() []*restful.WebService
|
|
|
|
|
|
|
|
// RegisteredHandlePaths returns the paths of handlers registered directly with the container (non-web-services)
|
|
|
|
// Used to test filters are being applied on non-web-service handlers
|
|
|
|
RegisteredHandlePaths() []string
|
|
|
|
}
|
|
|
|
|
|
|
|
// filteringContainer delegates all Handle(...) calls to Container.HandleWithFilter(...),
|
|
|
|
// so we can ensure restful.FilterFunctions are used for all handlers
|
|
|
|
type filteringContainer struct {
|
|
|
|
*restful.Container
|
|
|
|
registeredHandlePaths []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *filteringContainer) Handle(path string, handler http.Handler) {
|
|
|
|
a.HandleWithFilter(path, handler)
|
|
|
|
a.registeredHandlePaths = append(a.registeredHandlePaths, path)
|
|
|
|
}
|
|
|
|
func (a *filteringContainer) RegisteredHandlePaths() []string {
|
|
|
|
return a.registeredHandlePaths
|
|
|
|
}
|
|
|
|
|
2014-09-02 10:00:28 +00:00
|
|
|
// ListenAndServeKubeletServer initializes a server to respond to HTTP network requests on the Kubelet.
|
2016-03-29 00:05:02 +00:00
|
|
|
func ListenAndServeKubeletServer(
|
|
|
|
host HostInterface,
|
|
|
|
resourceAnalyzer stats.ResourceAnalyzer,
|
|
|
|
address net.IP,
|
|
|
|
port uint,
|
|
|
|
tlsOptions *TLSOptions,
|
|
|
|
auth AuthInterface,
|
2017-02-28 18:43:08 +00:00
|
|
|
enableDebuggingHandlers,
|
|
|
|
enableContentionProfiling bool,
|
2016-11-04 18:50:51 +00:00
|
|
|
runtime kubecontainer.Runtime,
|
|
|
|
criHandler http.Handler) {
|
2015-04-08 20:57:19 +00:00
|
|
|
glog.Infof("Starting to listen on %s:%d", address, port)
|
2017-02-28 18:43:08 +00:00
|
|
|
handler := NewServer(host, resourceAnalyzer, auth, enableDebuggingHandlers, enableContentionProfiling, runtime, criHandler)
|
2014-07-15 20:24:41 +00:00
|
|
|
s := &http.Server{
|
2014-10-04 04:34:30 +00:00
|
|
|
Addr: net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)),
|
2014-07-15 20:24:41 +00:00
|
|
|
Handler: &handler,
|
|
|
|
MaxHeaderBytes: 1 << 20,
|
|
|
|
}
|
2015-03-05 21:30:52 +00:00
|
|
|
if tlsOptions != nil {
|
|
|
|
s.TLSConfig = tlsOptions.Config
|
2017-02-17 19:32:41 +00:00
|
|
|
// Passing empty strings as the cert and key files means no
|
|
|
|
// cert/keys are specified and GetCertificate in the TLSConfig
|
|
|
|
// should be called instead.
|
2015-03-05 21:30:52 +00:00
|
|
|
glog.Fatal(s.ListenAndServeTLS(tlsOptions.CertFile, tlsOptions.KeyFile))
|
|
|
|
} else {
|
|
|
|
glog.Fatal(s.ListenAndServe())
|
|
|
|
}
|
2014-07-15 20:24:41 +00:00
|
|
|
}
|
|
|
|
|
2015-04-02 04:41:32 +00:00
|
|
|
// ListenAndServeKubeletReadOnlyServer initializes a server to respond to HTTP network requests on the Kubelet.
|
2016-03-29 00:05:02 +00:00
|
|
|
func ListenAndServeKubeletReadOnlyServer(host HostInterface, resourceAnalyzer stats.ResourceAnalyzer, address net.IP, port uint, runtime kubecontainer.Runtime) {
|
2015-04-02 04:41:32 +00:00
|
|
|
glog.V(1).Infof("Starting to listen read-only on %s:%d", address, port)
|
2017-02-28 18:43:08 +00:00
|
|
|
s := NewServer(host, resourceAnalyzer, nil, false, false, runtime, nil)
|
2015-04-02 04:41:32 +00:00
|
|
|
|
|
|
|
server := &http.Server{
|
|
|
|
Addr: net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)),
|
2015-06-17 22:31:46 +00:00
|
|
|
Handler: &s,
|
2015-04-02 04:41:32 +00:00
|
|
|
MaxHeaderBytes: 1 << 20,
|
|
|
|
}
|
|
|
|
glog.Fatal(server.ListenAndServe())
|
|
|
|
}
|
|
|
|
|
2015-09-29 03:32:20 +00:00
|
|
|
// AuthInterface contains all methods required by the auth filters
|
|
|
|
type AuthInterface interface {
|
|
|
|
authenticator.Request
|
|
|
|
authorizer.RequestAttributesGetter
|
|
|
|
authorizer.Authorizer
|
|
|
|
}
|
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
// HostInterface contains all the kubelet methods required by the server.
|
2017-06-14 03:04:16 +00:00
|
|
|
// For testability.
|
2014-07-15 20:24:41 +00:00
|
|
|
type HostInterface interface {
|
2017-08-18 22:08:44 +00:00
|
|
|
stats.StatsProvider
|
2017-07-18 05:12:24 +00:00
|
|
|
GetVersionInfo() (*cadvisorapi.VersionInfo, error)
|
2015-10-16 03:00:28 +00:00
|
|
|
GetCachedMachineInfo() (*cadvisorapi.MachineInfo, error)
|
2016-11-18 20:50:58 +00:00
|
|
|
GetRunningPods() ([]*v1.Pod, error)
|
2015-01-14 23:22:21 +00:00
|
|
|
RunInContainer(name string, uid types.UID, container string, cmd []string) ([]byte, error)
|
2017-02-15 10:34:49 +00:00
|
|
|
ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error
|
|
|
|
AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error
|
2016-11-18 20:50:58 +00:00
|
|
|
GetKubeletContainerLogs(podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error
|
2014-07-15 07:04:30 +00:00
|
|
|
ServeLogs(w http.ResponseWriter, req *http.Request)
|
2017-01-07 05:06:19 +00:00
|
|
|
PortForward(name string, uid types.UID, port int32, stream io.ReadWriteCloser) error
|
2015-01-08 20:41:38 +00:00
|
|
|
StreamingConnectionIdleTimeout() time.Duration
|
2015-06-17 22:31:46 +00:00
|
|
|
ResyncInterval() time.Duration
|
2015-02-09 16:40:42 +00:00
|
|
|
GetHostname() string
|
2015-06-17 22:31:46 +00:00
|
|
|
LatestLoopEntryTime() time.Time
|
2017-02-15 10:34:49 +00:00
|
|
|
GetExec(podFullName string, podUID types.UID, containerName string, cmd []string, streamOpts remotecommandserver.Options) (*url.URL, error)
|
|
|
|
GetAttach(podFullName string, podUID types.UID, containerName string, streamOpts remotecommandserver.Options) (*url.URL, error)
|
2017-01-07 05:06:19 +00:00
|
|
|
GetPortForward(podName, podNamespace string, podUID types.UID, portForwardOpts portforward.V4Options) (*url.URL, error)
|
2014-06-08 23:10:29 +00:00
|
|
|
}
|
|
|
|
|
2014-09-02 10:00:28 +00:00
|
|
|
// NewServer initializes and configures a kubelet.Server object to handle HTTP requests.
|
2016-03-29 00:05:02 +00:00
|
|
|
func NewServer(
|
|
|
|
host HostInterface,
|
|
|
|
resourceAnalyzer stats.ResourceAnalyzer,
|
|
|
|
auth AuthInterface,
|
2017-02-28 18:43:08 +00:00
|
|
|
enableDebuggingHandlers,
|
|
|
|
enableContentionProfiling bool,
|
2016-11-04 18:50:51 +00:00
|
|
|
runtime kubecontainer.Runtime,
|
|
|
|
criHandler http.Handler) Server {
|
2014-08-20 18:24:51 +00:00
|
|
|
server := Server{
|
2016-01-14 19:19:26 +00:00
|
|
|
host: host,
|
|
|
|
resourceAnalyzer: resourceAnalyzer,
|
|
|
|
auth: auth,
|
|
|
|
restfulCont: &filteringContainer{Container: restful.NewContainer()},
|
2016-03-29 00:05:02 +00:00
|
|
|
runtime: runtime,
|
2015-09-29 03:32:20 +00:00
|
|
|
}
|
|
|
|
if auth != nil {
|
|
|
|
server.InstallAuthFilter()
|
2014-08-20 18:24:51 +00:00
|
|
|
}
|
|
|
|
server.InstallDefaultHandlers()
|
2014-10-09 23:26:34 +00:00
|
|
|
if enableDebuggingHandlers {
|
2016-11-04 18:50:51 +00:00
|
|
|
server.InstallDebuggingHandlers(criHandler)
|
2017-02-28 18:43:08 +00:00
|
|
|
if enableContentionProfiling {
|
|
|
|
goruntime.SetBlockProfileRate(1)
|
|
|
|
}
|
2017-09-28 08:37:49 +00:00
|
|
|
} else {
|
|
|
|
server.InstallDebuggingDisabledHandlers()
|
2014-10-09 23:26:34 +00:00
|
|
|
}
|
2014-08-20 18:24:51 +00:00
|
|
|
return server
|
|
|
|
}
|
|
|
|
|
2015-09-29 03:32:20 +00:00
|
|
|
// InstallAuthFilter installs authentication filters with the restful Container.
|
|
|
|
func (s *Server) InstallAuthFilter() {
|
|
|
|
s.restfulCont.Filter(func(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
|
|
// Authenticate
|
|
|
|
u, ok, err := s.auth.AuthenticateRequest(req.Request)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Unable to authenticate the request due to an error: %v", err)
|
|
|
|
resp.WriteErrorString(http.StatusUnauthorized, "Unauthorized")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
resp.WriteErrorString(http.StatusUnauthorized, "Unauthorized")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get authorization attributes
|
|
|
|
attrs := s.auth.GetRequestAttributes(u, req.Request)
|
|
|
|
|
|
|
|
// Authorize
|
2017-09-29 21:21:40 +00:00
|
|
|
decision, _, err := s.auth.Authorize(attrs)
|
2016-06-29 14:20:31 +00:00
|
|
|
if err != nil {
|
2016-11-03 05:13:00 +00:00
|
|
|
msg := fmt.Sprintf("Authorization error (user=%s, verb=%s, resource=%s, subresource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource())
|
2016-06-29 14:20:31 +00:00
|
|
|
glog.Errorf(msg, err)
|
|
|
|
resp.WriteErrorString(http.StatusInternalServerError, msg)
|
|
|
|
return
|
|
|
|
}
|
2017-09-29 21:21:40 +00:00
|
|
|
if decision != authorizer.DecisionAllow {
|
2016-11-03 05:13:00 +00:00
|
|
|
msg := fmt.Sprintf("Forbidden (user=%s, verb=%s, resource=%s, subresource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource())
|
2015-09-29 03:32:20 +00:00
|
|
|
glog.V(2).Info(msg)
|
|
|
|
resp.WriteErrorString(http.StatusForbidden, msg)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Continue
|
|
|
|
chain.ProcessFilter(req, resp)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
// InstallDefaultHandlers registers the default set of supported HTTP request
|
|
|
|
// patterns with the restful Container.
|
2014-08-20 18:24:51 +00:00
|
|
|
func (s *Server) InstallDefaultHandlers() {
|
2015-08-17 01:40:08 +00:00
|
|
|
healthz.InstallHandler(s.restfulCont,
|
2015-03-19 22:56:29 +00:00
|
|
|
healthz.PingHealthz,
|
2015-06-17 22:31:46 +00:00
|
|
|
healthz.NamedCheck("syncloop", s.syncLoopHealthCheck),
|
2015-03-19 22:56:29 +00:00
|
|
|
)
|
2017-06-29 22:58:07 +00:00
|
|
|
ws := new(restful.WebService)
|
2015-08-17 01:40:08 +00:00
|
|
|
ws.
|
|
|
|
Path("/pods").
|
|
|
|
Produces(restful.MIME_JSON)
|
|
|
|
ws.Route(ws.GET("").
|
|
|
|
To(s.getPods).
|
|
|
|
Operation("getPods"))
|
|
|
|
s.restfulCont.Add(ws)
|
|
|
|
|
2016-10-08 04:44:13 +00:00
|
|
|
s.restfulCont.Add(stats.CreateHandlers(statsPath, s.host, s.resourceAnalyzer))
|
|
|
|
s.restfulCont.Handle(metricsPath, prometheus.Handler())
|
2015-08-17 01:40:08 +00:00
|
|
|
|
2017-07-18 05:12:24 +00:00
|
|
|
// cAdvisor metrics are exposed under the secured handler as well
|
|
|
|
r := prometheus.NewRegistry()
|
|
|
|
r.MustRegister(metrics.NewPrometheusCollector(prometheusHostAdapter{s.host}, containerPrometheusLabels))
|
|
|
|
s.restfulCont.Handle(cadvisorMetricsPath,
|
|
|
|
promhttp.HandlerFor(r, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError}),
|
|
|
|
)
|
|
|
|
|
2018-03-19 20:20:40 +00:00
|
|
|
// prober metrics are exposed under a different endpoint
|
|
|
|
p := prometheus.NewRegistry()
|
|
|
|
p.MustRegister(prober.ProberResults)
|
|
|
|
s.restfulCont.Handle(proberMetricsPath,
|
|
|
|
promhttp.HandlerFor(p, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError}),
|
|
|
|
)
|
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
ws = new(restful.WebService)
|
|
|
|
ws.
|
2016-10-08 04:44:13 +00:00
|
|
|
Path(specPath).
|
2015-08-17 01:40:08 +00:00
|
|
|
Produces(restful.MIME_JSON)
|
|
|
|
ws.Route(ws.GET("").
|
|
|
|
To(s.getSpec).
|
|
|
|
Operation("getSpec").
|
2015-10-16 03:00:28 +00:00
|
|
|
Writes(cadvisorapi.MachineInfo{}))
|
2015-08-17 01:40:08 +00:00
|
|
|
s.restfulCont.Add(ws)
|
2014-10-09 23:26:34 +00:00
|
|
|
}
|
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
const pprofBasePath = "/debug/pprof/"
|
|
|
|
|
2017-01-13 01:27:23 +00:00
|
|
|
// InstallDebuggingHandlers registers the HTTP request patterns that serve logs or run commands/containers
|
2016-11-04 18:50:51 +00:00
|
|
|
func (s *Server) InstallDebuggingHandlers(criHandler http.Handler) {
|
2017-02-16 03:49:52 +00:00
|
|
|
glog.Infof("Adding debug handlers to kubelet server.")
|
2015-08-17 01:40:08 +00:00
|
|
|
|
2017-06-29 22:58:07 +00:00
|
|
|
ws := new(restful.WebService)
|
2015-08-17 01:40:08 +00:00
|
|
|
ws.
|
|
|
|
Path("/run")
|
|
|
|
ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}").
|
|
|
|
To(s.getRun).
|
|
|
|
Operation("getRun"))
|
|
|
|
ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}/{containerName}").
|
|
|
|
To(s.getRun).
|
|
|
|
Operation("getRun"))
|
|
|
|
s.restfulCont.Add(ws)
|
|
|
|
|
|
|
|
ws = new(restful.WebService)
|
|
|
|
ws.
|
|
|
|
Path("/exec")
|
Expose exec and logs via WebSockets
Not all clients and systems can support SPDY protocols. This commit adds
support for two new websocket protocols, one to handle streaming of pod
logs from a pod, and the other to allow exec to be tunneled over
websocket.
Browser support for chunked encoding is still poor, and web consoles
that wish to show pod logs may need to make compromises to display the
output. The /pods/<name>/log endpoint now supports websocket upgrade to
the 'binary.k8s.io' subprotocol, which sends chunks of logs as binary to
the client. Messages are written as logs are streamed from the container
daemon, so flushing should be unaffected.
Browser support for raw communication over SDPY is not possible, and
some languages lack libraries for it and HTTP/2. The Kubelet supports
upgrade to WebSocket instead of SPDY, and will multiplex STDOUT/IN/ERR
over websockets by prepending each binary message with a single byte
representing the channel (0 for IN, 1 for OUT, and 2 for ERR). Because
framing on WebSockets suffers from head-of-line blocking, clients and
other server code should ensure that no particular stream blocks. An
alternative subprotocol 'base64.channel.k8s.io' base64 encodes the body
and uses '0'-'9' to represent the channel for ease of use in browsers.
2015-09-11 20:09:51 +00:00
|
|
|
ws.Route(ws.GET("/{podNamespace}/{podID}/{containerName}").
|
|
|
|
To(s.getExec).
|
|
|
|
Operation("getExec"))
|
2015-08-17 01:40:08 +00:00
|
|
|
ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}").
|
|
|
|
To(s.getExec).
|
|
|
|
Operation("getExec"))
|
Expose exec and logs via WebSockets
Not all clients and systems can support SPDY protocols. This commit adds
support for two new websocket protocols, one to handle streaming of pod
logs from a pod, and the other to allow exec to be tunneled over
websocket.
Browser support for chunked encoding is still poor, and web consoles
that wish to show pod logs may need to make compromises to display the
output. The /pods/<name>/log endpoint now supports websocket upgrade to
the 'binary.k8s.io' subprotocol, which sends chunks of logs as binary to
the client. Messages are written as logs are streamed from the container
daemon, so flushing should be unaffected.
Browser support for raw communication over SDPY is not possible, and
some languages lack libraries for it and HTTP/2. The Kubelet supports
upgrade to WebSocket instead of SPDY, and will multiplex STDOUT/IN/ERR
over websockets by prepending each binary message with a single byte
representing the channel (0 for IN, 1 for OUT, and 2 for ERR). Because
framing on WebSockets suffers from head-of-line blocking, clients and
other server code should ensure that no particular stream blocks. An
alternative subprotocol 'base64.channel.k8s.io' base64 encodes the body
and uses '0'-'9' to represent the channel for ease of use in browsers.
2015-09-11 20:09:51 +00:00
|
|
|
ws.Route(ws.GET("/{podNamespace}/{podID}/{uid}/{containerName}").
|
|
|
|
To(s.getExec).
|
|
|
|
Operation("getExec"))
|
2015-08-17 01:40:08 +00:00
|
|
|
ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}/{containerName}").
|
|
|
|
To(s.getExec).
|
|
|
|
Operation("getExec"))
|
|
|
|
s.restfulCont.Add(ws)
|
|
|
|
|
|
|
|
ws = new(restful.WebService)
|
|
|
|
ws.
|
|
|
|
Path("/attach")
|
Expose exec and logs via WebSockets
Not all clients and systems can support SPDY protocols. This commit adds
support for two new websocket protocols, one to handle streaming of pod
logs from a pod, and the other to allow exec to be tunneled over
websocket.
Browser support for chunked encoding is still poor, and web consoles
that wish to show pod logs may need to make compromises to display the
output. The /pods/<name>/log endpoint now supports websocket upgrade to
the 'binary.k8s.io' subprotocol, which sends chunks of logs as binary to
the client. Messages are written as logs are streamed from the container
daemon, so flushing should be unaffected.
Browser support for raw communication over SDPY is not possible, and
some languages lack libraries for it and HTTP/2. The Kubelet supports
upgrade to WebSocket instead of SPDY, and will multiplex STDOUT/IN/ERR
over websockets by prepending each binary message with a single byte
representing the channel (0 for IN, 1 for OUT, and 2 for ERR). Because
framing on WebSockets suffers from head-of-line blocking, clients and
other server code should ensure that no particular stream blocks. An
alternative subprotocol 'base64.channel.k8s.io' base64 encodes the body
and uses '0'-'9' to represent the channel for ease of use in browsers.
2015-09-11 20:09:51 +00:00
|
|
|
ws.Route(ws.GET("/{podNamespace}/{podID}/{containerName}").
|
|
|
|
To(s.getAttach).
|
|
|
|
Operation("getAttach"))
|
2015-08-17 01:40:08 +00:00
|
|
|
ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}").
|
|
|
|
To(s.getAttach).
|
|
|
|
Operation("getAttach"))
|
Expose exec and logs via WebSockets
Not all clients and systems can support SPDY protocols. This commit adds
support for two new websocket protocols, one to handle streaming of pod
logs from a pod, and the other to allow exec to be tunneled over
websocket.
Browser support for chunked encoding is still poor, and web consoles
that wish to show pod logs may need to make compromises to display the
output. The /pods/<name>/log endpoint now supports websocket upgrade to
the 'binary.k8s.io' subprotocol, which sends chunks of logs as binary to
the client. Messages are written as logs are streamed from the container
daemon, so flushing should be unaffected.
Browser support for raw communication over SDPY is not possible, and
some languages lack libraries for it and HTTP/2. The Kubelet supports
upgrade to WebSocket instead of SPDY, and will multiplex STDOUT/IN/ERR
over websockets by prepending each binary message with a single byte
representing the channel (0 for IN, 1 for OUT, and 2 for ERR). Because
framing on WebSockets suffers from head-of-line blocking, clients and
other server code should ensure that no particular stream blocks. An
alternative subprotocol 'base64.channel.k8s.io' base64 encodes the body
and uses '0'-'9' to represent the channel for ease of use in browsers.
2015-09-11 20:09:51 +00:00
|
|
|
ws.Route(ws.GET("/{podNamespace}/{podID}/{uid}/{containerName}").
|
|
|
|
To(s.getAttach).
|
|
|
|
Operation("getAttach"))
|
2015-08-17 01:40:08 +00:00
|
|
|
ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}/{containerName}").
|
|
|
|
To(s.getAttach).
|
|
|
|
Operation("getAttach"))
|
|
|
|
s.restfulCont.Add(ws)
|
|
|
|
|
|
|
|
ws = new(restful.WebService)
|
|
|
|
ws.
|
|
|
|
Path("/portForward")
|
2016-10-17 08:50:20 +00:00
|
|
|
ws.Route(ws.GET("/{podNamespace}/{podID}").
|
|
|
|
To(s.getPortForward).
|
|
|
|
Operation("getPortForward"))
|
2015-08-17 01:40:08 +00:00
|
|
|
ws.Route(ws.POST("/{podNamespace}/{podID}").
|
|
|
|
To(s.getPortForward).
|
|
|
|
Operation("getPortForward"))
|
2016-10-17 08:50:20 +00:00
|
|
|
ws.Route(ws.GET("/{podNamespace}/{podID}/{uid}").
|
|
|
|
To(s.getPortForward).
|
|
|
|
Operation("getPortForward"))
|
2015-08-17 01:40:08 +00:00
|
|
|
ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}").
|
|
|
|
To(s.getPortForward).
|
|
|
|
Operation("getPortForward"))
|
|
|
|
s.restfulCont.Add(ws)
|
|
|
|
|
|
|
|
ws = new(restful.WebService)
|
|
|
|
ws.
|
2016-10-08 04:44:13 +00:00
|
|
|
Path(logsPath)
|
2015-08-17 01:40:08 +00:00
|
|
|
ws.Route(ws.GET("").
|
|
|
|
To(s.getLogs).
|
|
|
|
Operation("getLogs"))
|
2015-10-30 23:19:34 +00:00
|
|
|
ws.Route(ws.GET("/{logpath:*}").
|
|
|
|
To(s.getLogs).
|
2016-09-01 23:39:10 +00:00
|
|
|
Operation("getLogs").
|
|
|
|
Param(ws.PathParameter("logpath", "path to the log").DataType("string")))
|
2015-08-17 01:40:08 +00:00
|
|
|
s.restfulCont.Add(ws)
|
|
|
|
|
|
|
|
ws = new(restful.WebService)
|
|
|
|
ws.
|
|
|
|
Path("/containerLogs")
|
|
|
|
ws.Route(ws.GET("/{podNamespace}/{podID}/{containerName}").
|
|
|
|
To(s.getContainerLogs).
|
|
|
|
Operation("getContainerLogs"))
|
|
|
|
s.restfulCont.Add(ws)
|
|
|
|
|
2016-02-02 02:09:02 +00:00
|
|
|
configz.InstallHandler(s.restfulCont)
|
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
handlePprofEndpoint := func(req *restful.Request, resp *restful.Response) {
|
|
|
|
name := strings.TrimPrefix(req.Request.URL.Path, pprofBasePath)
|
|
|
|
switch name {
|
|
|
|
case "profile":
|
|
|
|
pprof.Profile(resp, req.Request)
|
|
|
|
case "symbol":
|
|
|
|
pprof.Symbol(resp, req.Request)
|
|
|
|
case "cmdline":
|
|
|
|
pprof.Cmdline(resp, req.Request)
|
2017-02-28 18:43:08 +00:00
|
|
|
case "trace":
|
|
|
|
pprof.Trace(resp, req.Request)
|
2015-08-17 01:40:08 +00:00
|
|
|
default:
|
|
|
|
pprof.Index(resp, req.Request)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-16 13:42:29 +00:00
|
|
|
// Setup pprof handlers.
|
2015-08-17 01:40:08 +00:00
|
|
|
ws = new(restful.WebService).Path(pprofBasePath)
|
|
|
|
ws.Route(ws.GET("/{subpath:*}").To(func(req *restful.Request, resp *restful.Response) {
|
|
|
|
handlePprofEndpoint(req, resp)
|
|
|
|
})).Doc("pprof endpoint")
|
|
|
|
s.restfulCont.Add(ws)
|
|
|
|
|
2015-06-23 23:01:12 +00:00
|
|
|
// The /runningpods endpoint is used for testing only.
|
2015-08-17 01:40:08 +00:00
|
|
|
ws = new(restful.WebService)
|
|
|
|
ws.
|
|
|
|
Path("/runningpods/").
|
|
|
|
Produces(restful.MIME_JSON)
|
|
|
|
ws.Route(ws.GET("").
|
|
|
|
To(s.getRunningPods).
|
|
|
|
Operation("getRunningPods"))
|
|
|
|
s.restfulCont.Add(ws)
|
2015-06-04 06:51:14 +00:00
|
|
|
|
2016-11-04 18:50:51 +00:00
|
|
|
if criHandler != nil {
|
|
|
|
s.restfulCont.Handle("/cri/", criHandler)
|
|
|
|
}
|
2014-08-20 18:24:51 +00:00
|
|
|
}
|
|
|
|
|
2017-09-28 08:37:49 +00:00
|
|
|
// InstallDebuggingDisabledHandlers registers the HTTP request patterns that provide better error message
|
|
|
|
func (s *Server) InstallDebuggingDisabledHandlers() {
|
|
|
|
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
http.Error(w, "Debug endpoints are disabled.", http.StatusMethodNotAllowed)
|
|
|
|
})
|
|
|
|
|
|
|
|
paths := []string{
|
|
|
|
"/run/", "/exec/", "/attach/", "/portForward/", "/containerLogs/",
|
|
|
|
"/runningpods/", pprofBasePath, logsPath}
|
|
|
|
for _, p := range paths {
|
|
|
|
s.restfulCont.Handle(p, h)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-17 22:31:46 +00:00
|
|
|
// Checks if kubelet's sync loop that updates containers is working.
|
|
|
|
func (s *Server) syncLoopHealthCheck(req *http.Request) error {
|
|
|
|
duration := s.host.ResyncInterval() * 2
|
|
|
|
minDuration := time.Minute * 5
|
|
|
|
if duration < minDuration {
|
|
|
|
duration = minDuration
|
|
|
|
}
|
|
|
|
enterLoopTime := s.host.LatestLoopEntryTime()
|
|
|
|
if !enterLoopTime.IsZero() && time.Now().After(enterLoopTime.Add(duration)) {
|
2017-06-29 22:58:07 +00:00
|
|
|
return fmt.Errorf("sync Loop took longer than expected")
|
2015-06-17 22:31:46 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
// getContainerLogs handles containerLogs request against the Kubelet
|
|
|
|
func (s *Server) getContainerLogs(request *restful.Request, response *restful.Response) {
|
|
|
|
podNamespace := request.PathParameter("podNamespace")
|
|
|
|
podID := request.PathParameter("podID")
|
|
|
|
containerName := request.PathParameter("containerName")
|
2014-08-27 19:41:32 +00:00
|
|
|
|
2014-09-17 19:00:09 +00:00
|
|
|
if len(podID) == 0 {
|
2015-08-17 01:40:08 +00:00
|
|
|
// TODO: Why return JSON when the rest return plaintext errors?
|
2015-09-10 03:46:11 +00:00
|
|
|
// TODO: Why return plaintext errors?
|
2015-08-17 01:40:08 +00:00
|
|
|
response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podID."}`))
|
2014-09-17 19:00:09 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if len(containerName) == 0 {
|
2015-08-17 01:40:08 +00:00
|
|
|
// TODO: Why return JSON when the rest return plaintext errors?
|
|
|
|
response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing container name."}`))
|
2014-08-27 19:41:32 +00:00
|
|
|
return
|
|
|
|
}
|
2014-10-17 19:51:57 +00:00
|
|
|
if len(podNamespace) == 0 {
|
2015-08-17 01:40:08 +00:00
|
|
|
// TODO: Why return JSON when the rest return plaintext errors?
|
|
|
|
response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podNamespace."}`))
|
2014-10-17 19:51:57 +00:00
|
|
|
return
|
|
|
|
}
|
2014-09-24 21:27:10 +00:00
|
|
|
|
2015-09-10 03:46:11 +00:00
|
|
|
query := request.Request.URL.Query()
|
|
|
|
// backwards compatibility for the "tail" query parameter
|
|
|
|
if tail := request.QueryParameter("tail"); len(tail) > 0 {
|
|
|
|
query["tailLines"] = []string{tail}
|
|
|
|
// "all" is the same as omitting tail
|
|
|
|
if tail == "all" {
|
|
|
|
delete(query, "tailLines")
|
|
|
|
}
|
|
|
|
}
|
2016-01-22 05:11:30 +00:00
|
|
|
// container logs on the kubelet are locked to the v1 API version of PodLogOptions
|
2016-11-18 20:50:58 +00:00
|
|
|
logOptions := &v1.PodLogOptions{}
|
2017-10-16 11:41:50 +00:00
|
|
|
if err := legacyscheme.ParameterCodec.DecodeParameters(query, v1.SchemeGroupVersion, logOptions); err != nil {
|
2015-09-10 03:46:11 +00:00
|
|
|
response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Unable to decode query."}`))
|
|
|
|
return
|
|
|
|
}
|
2016-12-03 18:57:26 +00:00
|
|
|
logOptions.TypeMeta = metav1.TypeMeta{}
|
2015-09-10 03:46:11 +00:00
|
|
|
if errs := validation.ValidatePodLogOptions(logOptions); len(errs) > 0 {
|
2017-07-27 01:43:24 +00:00
|
|
|
response.WriteError(http.StatusUnprocessableEntity, fmt.Errorf(`{"message": "Invalid request."}`))
|
2015-09-10 03:46:11 +00:00
|
|
|
return
|
|
|
|
}
|
2014-09-15 16:20:01 +00:00
|
|
|
|
2015-01-07 15:18:56 +00:00
|
|
|
pod, ok := s.host.GetPodByName(podNamespace, podID)
|
|
|
|
if !ok {
|
2016-01-29 03:27:56 +00:00
|
|
|
response.WriteError(http.StatusNotFound, fmt.Errorf("pod %q does not exist\n", podID))
|
2015-02-12 01:03:59 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
// Check if containerName is valid.
|
|
|
|
containerExists := false
|
|
|
|
for _, container := range pod.Spec.Containers {
|
|
|
|
if container.Name == containerName {
|
|
|
|
containerExists = true
|
2017-01-13 01:34:31 +00:00
|
|
|
break
|
2015-02-12 01:03:59 +00:00
|
|
|
}
|
|
|
|
}
|
2016-03-29 03:08:54 +00:00
|
|
|
if !containerExists {
|
|
|
|
for _, container := range pod.Spec.InitContainers {
|
|
|
|
if container.Name == containerName {
|
|
|
|
containerExists = true
|
2017-01-13 01:34:31 +00:00
|
|
|
break
|
2016-03-29 03:08:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-02-12 01:03:59 +00:00
|
|
|
if !containerExists {
|
2016-01-29 03:27:56 +00:00
|
|
|
response.WriteError(http.StatusNotFound, fmt.Errorf("container %q not found in pod %q\n", containerName, podID))
|
2015-01-07 15:18:56 +00:00
|
|
|
return
|
|
|
|
}
|
2014-09-17 19:00:09 +00:00
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
if _, ok := response.ResponseWriter.(http.Flusher); !ok {
|
2016-01-29 03:27:56 +00:00
|
|
|
response.WriteError(http.StatusInternalServerError, fmt.Errorf("unable to convert %v into http.Flusher, cannot show logs\n", reflect.TypeOf(response)))
|
2015-04-06 15:23:56 +00:00
|
|
|
return
|
2014-08-27 19:41:32 +00:00
|
|
|
}
|
2015-09-11 16:28:31 +00:00
|
|
|
fw := flushwriter.Wrap(response.ResponseWriter)
|
2016-10-20 22:35:34 +00:00
|
|
|
// Byte limit logic is already implemented in kuberuntime. However, we still need this for
|
|
|
|
// old runtime integration.
|
|
|
|
// TODO(random-liu): Remove this once we switch to CRI integration.
|
2015-09-10 03:46:11 +00:00
|
|
|
if logOptions.LimitBytes != nil {
|
|
|
|
fw = limitwriter.New(fw, *logOptions.LimitBytes)
|
|
|
|
}
|
2015-08-17 01:40:08 +00:00
|
|
|
response.Header().Set("Transfer-Encoding", "chunked")
|
2015-09-10 03:46:11 +00:00
|
|
|
if err := s.host.GetKubeletContainerLogs(kubecontainer.GetPodFullName(pod), containerName, logOptions, fw, fw); err != nil {
|
|
|
|
if err != limitwriter.ErrMaximumWrite {
|
2016-01-29 03:27:56 +00:00
|
|
|
response.WriteError(http.StatusBadRequest, err)
|
2015-09-10 03:46:11 +00:00
|
|
|
}
|
2014-08-27 19:41:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-18 20:50:58 +00:00
|
|
|
// encodePods creates an v1.PodList object from pods and returns the encoded
|
2015-06-23 23:01:12 +00:00
|
|
|
// PodList.
|
2016-11-18 20:50:58 +00:00
|
|
|
func encodePods(pods []*v1.Pod) (data []byte, err error) {
|
|
|
|
podList := new(v1.PodList)
|
2015-04-03 22:51:50 +00:00
|
|
|
for _, pod := range pods {
|
|
|
|
podList.Items = append(podList.Items, *pod)
|
2014-10-22 23:52:38 +00:00
|
|
|
}
|
2016-01-22 05:11:30 +00:00
|
|
|
// TODO: this needs to be parameterized to the kubelet, not hardcoded. Depends on Kubelet
|
|
|
|
// as API server refactor.
|
|
|
|
// TODO: Locked to v1, needs to be made generic
|
2017-10-16 11:41:50 +00:00
|
|
|
codec := legacyscheme.Codecs.LegacyCodec(schema.GroupVersion{Group: v1.GroupName, Version: "v1"})
|
2016-01-22 05:11:30 +00:00
|
|
|
return runtime.Encode(codec, podList)
|
2015-06-23 23:01:12 +00:00
|
|
|
}
|
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
// getPods returns a list of pods bound to the Kubelet and their spec.
|
|
|
|
func (s *Server) getPods(request *restful.Request, response *restful.Response) {
|
2015-06-23 23:01:12 +00:00
|
|
|
pods := s.host.GetPods()
|
|
|
|
data, err := encodePods(pods)
|
|
|
|
if err != nil {
|
2015-08-17 01:40:08 +00:00
|
|
|
response.WriteError(http.StatusInternalServerError, err)
|
2015-06-23 23:01:12 +00:00
|
|
|
return
|
|
|
|
}
|
2016-03-15 01:04:17 +00:00
|
|
|
writeJsonResponse(response, data)
|
2015-06-23 23:01:12 +00:00
|
|
|
}
|
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
// getRunningPods returns a list of pods running on Kubelet. The list is
|
2015-06-23 23:01:12 +00:00
|
|
|
// provided by the container runtime, and is different from the list returned
|
2015-08-17 01:40:08 +00:00
|
|
|
// by getPods, which is a set of desired pods to run.
|
|
|
|
func (s *Server) getRunningPods(request *restful.Request, response *restful.Response) {
|
2015-06-23 23:01:12 +00:00
|
|
|
pods, err := s.host.GetRunningPods()
|
|
|
|
if err != nil {
|
2015-08-17 01:40:08 +00:00
|
|
|
response.WriteError(http.StatusInternalServerError, err)
|
2015-06-23 23:01:12 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
data, err := encodePods(pods)
|
2014-10-22 23:52:38 +00:00
|
|
|
if err != nil {
|
2015-08-17 01:40:08 +00:00
|
|
|
response.WriteError(http.StatusInternalServerError, err)
|
2014-10-22 23:52:38 +00:00
|
|
|
return
|
|
|
|
}
|
2016-03-15 01:04:17 +00:00
|
|
|
writeJsonResponse(response, data)
|
2014-10-22 23:52:38 +00:00
|
|
|
}
|
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
// getLogs handles logs requests against the Kubelet.
|
|
|
|
func (s *Server) getLogs(request *restful.Request, response *restful.Response) {
|
|
|
|
s.host.ServeLogs(response, request.Request)
|
2014-08-20 18:24:51 +00:00
|
|
|
}
|
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
// getSpec handles spec requests against the Kubelet.
|
|
|
|
func (s *Server) getSpec(request *restful.Request, response *restful.Response) {
|
2015-09-21 18:06:38 +00:00
|
|
|
info, err := s.host.GetCachedMachineInfo()
|
2014-08-20 18:24:51 +00:00
|
|
|
if err != nil {
|
2015-08-17 01:40:08 +00:00
|
|
|
response.WriteError(http.StatusInternalServerError, err)
|
2014-08-20 18:24:51 +00:00
|
|
|
return
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
2015-08-17 01:40:08 +00:00
|
|
|
response.WriteEntity(info)
|
2014-08-20 18:24:51 +00:00
|
|
|
}
|
|
|
|
|
2017-01-07 05:06:19 +00:00
|
|
|
type execRequestParams struct {
|
2016-11-03 00:42:00 +00:00
|
|
|
podNamespace string
|
|
|
|
podName string
|
|
|
|
podUID types.UID
|
|
|
|
containerName string
|
|
|
|
cmd []string
|
|
|
|
}
|
|
|
|
|
2017-01-07 05:06:19 +00:00
|
|
|
func getExecRequestParams(req *restful.Request) execRequestParams {
|
|
|
|
return execRequestParams{
|
2016-11-03 00:42:00 +00:00
|
|
|
podNamespace: req.PathParameter("podNamespace"),
|
|
|
|
podName: req.PathParameter("podID"),
|
|
|
|
podUID: types.UID(req.PathParameter("uid")),
|
|
|
|
containerName: req.PathParameter("containerName"),
|
2017-05-05 22:52:46 +00:00
|
|
|
cmd: req.Request.URL.Query()[api.ExecCommandParam],
|
2015-01-08 20:41:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-07 05:06:19 +00:00
|
|
|
type portForwardRequestParams struct {
|
|
|
|
podNamespace string
|
|
|
|
podName string
|
|
|
|
podUID types.UID
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPortForwardRequestParams(req *restful.Request) portForwardRequestParams {
|
|
|
|
return portForwardRequestParams{
|
|
|
|
podNamespace: req.PathParameter("podNamespace"),
|
|
|
|
podName: req.PathParameter("podID"),
|
|
|
|
podUID: types.UID(req.PathParameter("uid")),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
// getAttach handles requests to attach to a container.
|
2015-08-17 01:40:08 +00:00
|
|
|
func (s *Server) getAttach(request *restful.Request, response *restful.Response) {
|
2017-01-07 05:06:19 +00:00
|
|
|
params := getExecRequestParams(request)
|
2017-02-15 10:34:49 +00:00
|
|
|
streamOpts, err := remotecommandserver.NewOptions(request.Request)
|
2016-12-09 02:54:02 +00:00
|
|
|
if err != nil {
|
|
|
|
utilruntime.HandleError(err)
|
|
|
|
response.WriteError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
2016-11-03 00:42:00 +00:00
|
|
|
pod, ok := s.host.GetPodByName(params.podNamespace, params.podName)
|
2015-07-28 04:48:55 +00:00
|
|
|
if !ok {
|
2015-08-17 01:40:08 +00:00
|
|
|
response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist"))
|
2015-07-28 04:48:55 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-03 00:42:00 +00:00
|
|
|
podFullName := kubecontainer.GetPodFullName(pod)
|
2016-12-09 02:54:02 +00:00
|
|
|
redirect, err := s.host.GetAttach(podFullName, params.podUID, params.containerName, *streamOpts)
|
2016-11-03 00:42:00 +00:00
|
|
|
if err != nil {
|
2016-12-14 02:27:05 +00:00
|
|
|
streaming.WriteError(err, response.ResponseWriter)
|
2016-11-03 00:42:00 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if redirect != nil {
|
|
|
|
http.Redirect(response.ResponseWriter, request.Request, redirect.String(), http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-02-15 10:34:49 +00:00
|
|
|
remotecommandserver.ServeAttach(response.ResponseWriter,
|
2016-03-22 13:38:42 +00:00
|
|
|
request.Request,
|
|
|
|
s.host,
|
2016-11-03 00:42:00 +00:00
|
|
|
podFullName,
|
|
|
|
params.podUID,
|
|
|
|
params.containerName,
|
2016-12-09 02:54:02 +00:00
|
|
|
streamOpts,
|
2016-03-22 13:38:42 +00:00
|
|
|
s.host.StreamingConnectionIdleTimeout(),
|
2017-02-15 10:34:49 +00:00
|
|
|
remotecommandconsts.DefaultStreamCreationTimeout,
|
|
|
|
remotecommandconsts.SupportedStreamingProtocols)
|
2016-03-22 13:38:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// getExec handles requests to run a command inside a container.
|
|
|
|
func (s *Server) getExec(request *restful.Request, response *restful.Response) {
|
2017-01-07 05:06:19 +00:00
|
|
|
params := getExecRequestParams(request)
|
2017-02-15 10:34:49 +00:00
|
|
|
streamOpts, err := remotecommandserver.NewOptions(request.Request)
|
2016-12-09 02:54:02 +00:00
|
|
|
if err != nil {
|
|
|
|
utilruntime.HandleError(err)
|
|
|
|
response.WriteError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
2016-11-03 00:42:00 +00:00
|
|
|
pod, ok := s.host.GetPodByName(params.podNamespace, params.podName)
|
2015-07-28 04:48:55 +00:00
|
|
|
if !ok {
|
2016-03-22 13:38:42 +00:00
|
|
|
response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist"))
|
2015-07-28 04:48:55 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-03 00:42:00 +00:00
|
|
|
podFullName := kubecontainer.GetPodFullName(pod)
|
2016-12-09 02:54:02 +00:00
|
|
|
redirect, err := s.host.GetExec(podFullName, params.podUID, params.containerName, params.cmd, *streamOpts)
|
2016-11-03 00:42:00 +00:00
|
|
|
if err != nil {
|
2016-12-14 02:27:05 +00:00
|
|
|
streaming.WriteError(err, response.ResponseWriter)
|
2016-11-03 00:42:00 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if redirect != nil {
|
|
|
|
http.Redirect(response.ResponseWriter, request.Request, redirect.String(), http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-02-15 10:34:49 +00:00
|
|
|
remotecommandserver.ServeExec(response.ResponseWriter,
|
2016-03-22 13:38:42 +00:00
|
|
|
request.Request,
|
|
|
|
s.host,
|
2016-11-03 00:42:00 +00:00
|
|
|
podFullName,
|
|
|
|
params.podUID,
|
|
|
|
params.containerName,
|
2016-12-09 02:54:02 +00:00
|
|
|
params.cmd,
|
|
|
|
streamOpts,
|
2016-03-22 13:38:42 +00:00
|
|
|
s.host.StreamingConnectionIdleTimeout(),
|
2017-02-15 10:34:49 +00:00
|
|
|
remotecommandconsts.DefaultStreamCreationTimeout,
|
|
|
|
remotecommandconsts.SupportedStreamingProtocols)
|
2015-07-28 04:48:55 +00:00
|
|
|
}
|
|
|
|
|
2015-08-17 01:40:08 +00:00
|
|
|
// getRun handles requests to run a command inside a container.
|
|
|
|
func (s *Server) getRun(request *restful.Request, response *restful.Response) {
|
2017-01-07 05:06:19 +00:00
|
|
|
params := getExecRequestParams(request)
|
2016-11-03 00:42:00 +00:00
|
|
|
pod, ok := s.host.GetPodByName(params.podNamespace, params.podName)
|
2015-01-07 15:18:56 +00:00
|
|
|
if !ok {
|
2015-08-17 01:40:08 +00:00
|
|
|
response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist"))
|
2015-01-07 15:18:56 +00:00
|
|
|
return
|
|
|
|
}
|
2016-11-03 00:42:00 +00:00
|
|
|
|
|
|
|
// For legacy reasons, run uses different query param than exec.
|
|
|
|
params.cmd = strings.Split(request.QueryParameter("cmd"), " ")
|
|
|
|
data, err := s.host.RunInContainer(kubecontainer.GetPodFullName(pod), params.podUID, params.containerName, params.cmd)
|
2014-08-26 19:57:44 +00:00
|
|
|
if err != nil {
|
2015-08-17 01:40:08 +00:00
|
|
|
response.WriteError(http.StatusInternalServerError, err)
|
2014-08-26 19:57:44 +00:00
|
|
|
return
|
|
|
|
}
|
2016-03-15 01:04:17 +00:00
|
|
|
writeJsonResponse(response, data)
|
2014-08-26 19:57:44 +00:00
|
|
|
}
|
|
|
|
|
2016-03-15 01:04:17 +00:00
|
|
|
// Derived from go-restful writeJSON.
|
|
|
|
func writeJsonResponse(response *restful.Response, data []byte) {
|
|
|
|
if data == nil {
|
|
|
|
response.WriteHeader(http.StatusOK)
|
|
|
|
// do not write a nil representation
|
|
|
|
return
|
|
|
|
}
|
|
|
|
response.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
|
|
|
|
response.WriteHeader(http.StatusOK)
|
|
|
|
if _, err := response.Write(data); err != nil {
|
|
|
|
glog.Errorf("Error writing response: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-22 20:29:51 +00:00
|
|
|
// getPortForward handles a new restful port forward request. It determines the
|
|
|
|
// pod name and uid and then calls ServePortForward.
|
2015-08-17 01:40:08 +00:00
|
|
|
func (s *Server) getPortForward(request *restful.Request, response *restful.Response) {
|
2017-01-07 05:06:19 +00:00
|
|
|
params := getPortForwardRequestParams(request)
|
|
|
|
|
|
|
|
portForwardOptions, err := portforward.NewV4Options(request.Request)
|
|
|
|
if err != nil {
|
|
|
|
utilruntime.HandleError(err)
|
|
|
|
response.WriteError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
2016-11-03 00:42:00 +00:00
|
|
|
pod, ok := s.host.GetPodByName(params.podNamespace, params.podName)
|
2015-01-08 20:41:38 +00:00
|
|
|
if !ok {
|
2015-08-17 01:40:08 +00:00
|
|
|
response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist"))
|
2015-01-08 20:41:38 +00:00
|
|
|
return
|
|
|
|
}
|
2016-11-04 18:50:51 +00:00
|
|
|
if len(params.podUID) > 0 && pod.UID != params.podUID {
|
|
|
|
response.WriteError(http.StatusNotFound, fmt.Errorf("pod not found"))
|
|
|
|
return
|
|
|
|
}
|
2015-01-08 20:41:38 +00:00
|
|
|
|
2017-01-07 05:06:19 +00:00
|
|
|
redirect, err := s.host.GetPortForward(pod.Name, pod.Namespace, pod.UID, *portForwardOptions)
|
2016-11-03 00:42:00 +00:00
|
|
|
if err != nil {
|
2016-12-14 02:27:05 +00:00
|
|
|
streaming.WriteError(err, response.ResponseWriter)
|
2016-11-03 00:42:00 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if redirect != nil {
|
|
|
|
http.Redirect(response.ResponseWriter, request.Request, redirect.String(), http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
2015-09-22 20:29:51 +00:00
|
|
|
|
2016-11-03 00:42:00 +00:00
|
|
|
portforward.ServePortForward(response.ResponseWriter,
|
|
|
|
request.Request,
|
|
|
|
s.host,
|
|
|
|
kubecontainer.GetPodFullName(pod),
|
|
|
|
params.podUID,
|
2017-01-07 05:06:19 +00:00
|
|
|
portForwardOptions,
|
2016-11-03 00:42:00 +00:00
|
|
|
s.host.StreamingConnectionIdleTimeout(),
|
2017-02-15 10:34:49 +00:00
|
|
|
remotecommandconsts.DefaultStreamCreationTimeout,
|
2016-10-17 08:50:20 +00:00
|
|
|
portforward.SupportedProtocols)
|
2015-01-08 20:41:38 +00:00
|
|
|
}
|
|
|
|
|
2014-09-02 10:00:28 +00:00
|
|
|
// ServeHTTP responds to HTTP requests on the Kubelet.
|
2014-08-20 18:24:51 +00:00
|
|
|
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
2014-08-21 04:27:19 +00:00
|
|
|
defer httplog.NewLogged(req, &w).StacktraceWhen(
|
2014-08-20 18:24:51 +00:00
|
|
|
httplog.StatusIsNot(
|
|
|
|
http.StatusOK,
|
2016-11-02 23:15:24 +00:00
|
|
|
http.StatusFound,
|
2014-12-18 02:42:11 +00:00
|
|
|
http.StatusMovedPermanently,
|
|
|
|
http.StatusTemporaryRedirect,
|
2016-10-21 18:04:20 +00:00
|
|
|
http.StatusBadRequest,
|
2014-08-20 18:24:51 +00:00
|
|
|
http.StatusNotFound,
|
2015-01-08 20:41:38 +00:00
|
|
|
http.StatusSwitchingProtocols,
|
2014-08-20 18:24:51 +00:00
|
|
|
),
|
|
|
|
).Log()
|
2015-08-17 01:40:08 +00:00
|
|
|
s.restfulCont.ServeHTTP(w, req)
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
2017-07-18 05:12:24 +00:00
|
|
|
|
|
|
|
// prometheusHostAdapter adapts the HostInterface to the interface expected by the
|
|
|
|
// cAdvisor prometheus collector.
|
|
|
|
type prometheusHostAdapter struct {
|
|
|
|
host HostInterface
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a prometheusHostAdapter) SubcontainersInfo(containerName string, query *cadvisorapi.ContainerInfoRequest) ([]*cadvisorapi.ContainerInfo, error) {
|
|
|
|
all, err := a.host.GetRawContainerInfo(containerName, query, true)
|
|
|
|
items := make([]*cadvisorapi.ContainerInfo, 0, len(all))
|
|
|
|
for _, v := range all {
|
|
|
|
items = append(items, v)
|
|
|
|
}
|
|
|
|
return items, err
|
|
|
|
}
|
|
|
|
func (a prometheusHostAdapter) GetVersionInfo() (*cadvisorapi.VersionInfo, error) {
|
|
|
|
return a.host.GetVersionInfo()
|
|
|
|
}
|
|
|
|
func (a prometheusHostAdapter) GetMachineInfo() (*cadvisorapi.MachineInfo, error) {
|
|
|
|
return a.host.GetCachedMachineInfo()
|
|
|
|
}
|
|
|
|
|
|
|
|
// containerPrometheusLabels maps cAdvisor labels to prometheus labels.
|
|
|
|
func containerPrometheusLabels(c *cadvisorapi.ContainerInfo) map[string]string {
|
2017-08-28 19:27:38 +00:00
|
|
|
// Prometheus requires that all metrics in the same family have the same labels,
|
|
|
|
// so we arrange to supply blank strings for missing labels
|
|
|
|
var name, image, podName, namespace, containerName string
|
2017-07-18 05:12:24 +00:00
|
|
|
if len(c.Aliases) > 0 {
|
2017-08-28 19:27:38 +00:00
|
|
|
name = c.Aliases[0]
|
2017-07-18 05:12:24 +00:00
|
|
|
}
|
2017-08-28 19:27:38 +00:00
|
|
|
image = c.Spec.Image
|
2017-07-18 05:12:24 +00:00
|
|
|
if v, ok := c.Spec.Labels[kubelettypes.KubernetesPodNameLabel]; ok {
|
2017-08-28 19:27:38 +00:00
|
|
|
podName = v
|
2017-07-18 05:12:24 +00:00
|
|
|
}
|
|
|
|
if v, ok := c.Spec.Labels[kubelettypes.KubernetesPodNamespaceLabel]; ok {
|
2017-08-28 19:27:38 +00:00
|
|
|
namespace = v
|
2017-07-18 05:12:24 +00:00
|
|
|
}
|
|
|
|
if v, ok := c.Spec.Labels[kubelettypes.KubernetesContainerNameLabel]; ok {
|
2017-08-28 19:27:38 +00:00
|
|
|
containerName = v
|
|
|
|
}
|
|
|
|
set := map[string]string{
|
|
|
|
metrics.LabelID: c.Name,
|
|
|
|
metrics.LabelName: name,
|
|
|
|
metrics.LabelImage: image,
|
|
|
|
"pod_name": podName,
|
|
|
|
"namespace": namespace,
|
|
|
|
"container_name": containerName,
|
2017-07-18 05:12:24 +00:00
|
|
|
}
|
|
|
|
return set
|
|
|
|
}
|