componentstatus: support client cert health check

etcd has support for client-cert-auth, which can be configured via the flag `--ca-file`,
when that is enabled, all the client requests must present with a client certificate,
however, the current component status check uses a single transport for all of the checks,
this is wrong, the checks should be different for each of different component, and make
each of them use different transport(tls configurations).
pull/6/head
zhouhaibing089 2017-01-11 14:40:09 +08:00 committed by haibzhou
parent dee81ed56a
commit b1040171b6
9 changed files with 93 additions and 33 deletions

View File

@ -32,7 +32,12 @@ import (
func New() HTTPProber {
tlsConfig := &tls.Config{InsecureSkipVerify: true}
transport := utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: tlsConfig, DisableKeepAlives: true})
return NewWithTLSConfig(tlsConfig)
}
// NewWithTLSConfig takes tls config as parameter.
func NewWithTLSConfig(config *tls.Config) HTTPProber {
transport := utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: config, DisableKeepAlives: true})
return httpProber{transport}
}

View File

@ -27,19 +27,16 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/probe"
httpprober "k8s.io/kubernetes/pkg/probe/http"
)
type REST struct {
GetServersToValidate func() map[string]Server
prober httpprober.HTTPProber
GetServersToValidate func() map[string]*Server
}
// NewStorage returns a new REST.
func NewStorage(serverRetriever func() map[string]Server) *REST {
func NewStorage(serverRetriever func() map[string]*Server) *REST {
return &REST{
GetServersToValidate: serverRetriever,
prober: httpprober.New(),
}
}
@ -60,7 +57,7 @@ func (rs *REST) List(ctx genericapirequest.Context, options *metainternalversion
wait.Add(len(servers))
statuses := make(chan api.ComponentStatus, len(servers))
for k, v := range servers {
go func(name string, server Server) {
go func(name string, server *Server) {
defer wait.Done()
status := rs.getComponentStatus(name, server)
statuses <- *status
@ -97,8 +94,8 @@ func ToConditionStatus(s probe.Result) api.ConditionStatus {
}
}
func (rs *REST) getComponentStatus(name string, server Server) *api.ComponentStatus {
status, msg, err := server.DoServerCheck(rs.prober)
func (rs *REST) getComponentStatus(name string, server *Server) *api.ComponentStatus {
status, msg, err := server.DoServerCheck()
errorMsg := ""
if err != nil {
errorMsg = err.Error()

View File

@ -50,16 +50,16 @@ type testResponse struct {
}
func NewTestREST(resp testResponse) *REST {
return &REST{
GetServersToValidate: func() map[string]Server {
return map[string]Server{
"test1": {Addr: "testserver1", Port: 8000, Path: "/healthz"},
}
},
prober: &fakeHttpProber{
prober := &fakeHttpProber{
result: resp.result,
body: resp.data,
err: resp.err,
}
return &REST{
GetServersToValidate: func() map[string]*Server {
return map[string]*Server{
"test1": {Addr: "testserver1", Port: 8000, Path: "/healthz", Prober: prober},
}
},
}
}

View File

@ -17,8 +17,9 @@ limitations under the License.
package componentstatus
import (
"crypto/tls"
"net/http"
"sync"
"time"
utilnet "k8s.io/apimachinery/pkg/util/net"
@ -42,7 +43,10 @@ type Server struct {
Port int
Path string
EnableHTTPS bool
TLSConfig *tls.Config
Validate ValidatorFn
Prober httpprober.HTTPProber
Once sync.Once
}
type ServerStatus struct {
@ -58,14 +62,22 @@ type ServerStatus struct {
Err string `json:"err,omitempty"`
}
func (server *Server) DoServerCheck(prober httpprober.HTTPProber) (probe.Result, string, error) {
func (server *Server) DoServerCheck() (probe.Result, string, error) {
// setup the prober
server.Once.Do(func() {
if server.Prober != nil {
return
}
server.Prober = httpprober.NewWithTLSConfig(server.TLSConfig)
})
scheme := "http"
if server.EnableHTTPS {
scheme = "https"
}
url := utilnet.FormatURL(scheme, server.Addr, server.Port, server.Path)
result, data, err := prober.Probe(url, nil, probeTimeOut)
result, data, err := server.Prober.Probe(url, nil, probeTimeOut)
if err != nil {
return probe.Unknown, "", err

View File

@ -59,7 +59,8 @@ func TestValidate(t *testing.T) {
}
s.Validate = test.validator
result, data, err := s.DoServerCheck(fakeProber)
s.Prober = fakeProber
result, data, err := s.DoServerCheck()
if test.expectErr && err == nil {
t.Error("unexpected non-error")
}

View File

@ -15,6 +15,7 @@ go_test(
tags = ["automanaged"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
],
)

View File

@ -242,14 +242,14 @@ type componentStatusStorage struct {
storageFactory serverstorage.StorageFactory
}
func (s componentStatusStorage) serversToValidate() map[string]componentstatus.Server {
serversToValidate := map[string]componentstatus.Server{
func (s componentStatusStorage) serversToValidate() map[string]*componentstatus.Server {
serversToValidate := map[string]*componentstatus.Server{
"controller-manager": {Addr: "127.0.0.1", Port: ports.ControllerManagerPort, Path: "/healthz"},
"scheduler": {Addr: "127.0.0.1", Port: ports.SchedulerPort, Path: "/healthz"},
}
for ix, machine := range s.storageFactory.Backends() {
etcdUrl, err := url.Parse(machine)
etcdUrl, err := url.Parse(machine.Server)
if err != nil {
glog.Errorf("Failed to parse etcd url for validation: %v", err)
continue
@ -269,9 +269,10 @@ func (s componentStatusStorage) serversToValidate() map[string]componentstatus.S
port = 2379
}
// TODO: etcd health checking should be abstracted in the storage tier
serversToValidate[fmt.Sprintf("etcd-%d", ix)] = componentstatus.Server{
serversToValidate[fmt.Sprintf("etcd-%d", ix)] = &componentstatus.Server{
Addr: addr,
EnableHTTPS: etcdUrl.Scheme == "https",
TLSConfig: machine.TLSConfig,
Port: port,
Path: "/health",
Validate: etcdutil.EtcdHealthCheck,

View File

@ -20,6 +20,7 @@ import (
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/server/storage"
"k8s.io/apiserver/pkg/storage/storagebackend"
)
@ -47,6 +48,6 @@ func (f fakeStorageFactory) ResourcePrefix(groupResource schema.GroupResource) s
return ""
}
func (f fakeStorageFactory) Backends() []string {
return []string{"etcd-0"}
func (f fakeStorageFactory) Backends() []storage.Backend {
return []storage.Backend{{Server: "etcd-0"}}
}

View File

@ -17,6 +17,9 @@ limitations under the License.
package storage
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"strings"
"github.com/golang/glog"
@ -27,6 +30,15 @@ import (
"k8s.io/apiserver/pkg/storage/storagebackend"
)
// Backend describes the storage servers, the information here should be enough
// for health validations.
type Backend struct {
// the url of storage backend like: https://etcd.domain:2379
Server string
// the required tls config
TLSConfig *tls.Config
}
// StorageFactory is the interface to locate the storage for a given GroupResource
type StorageFactory interface {
// New finds the storage destination for the given group and resource. It will
@ -40,7 +52,7 @@ type StorageFactory interface {
// Backends gets all backends for all registered storage destinations.
// Used for getting all instances for health validations.
Backends() []string
Backends() []Backend
}
// DefaultStorageFactory takes a GroupResource and returns back its storage interface. This result includes:
@ -252,15 +264,45 @@ func (s *DefaultStorageFactory) NewConfig(groupResource schema.GroupResource) (*
return &storageConfig, nil
}
// Get all backends for all registered storage destinations.
// Backends returns all backends for all registered storage destinations.
// Used for getting all instances for health validations.
func (s *DefaultStorageFactory) Backends() []string {
backends := sets.NewString(s.StorageConfig.ServerList...)
func (s *DefaultStorageFactory) Backends() []Backend {
servers := sets.NewString(s.StorageConfig.ServerList...)
for _, overrides := range s.Overrides {
backends.Insert(overrides.etcdLocation...)
servers.Insert(overrides.etcdLocation...)
}
return backends.List()
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
if len(s.StorageConfig.CertFile) > 0 && len(s.StorageConfig.KeyFile) > 0 {
cert, err := tls.LoadX509KeyPair(s.StorageConfig.CertFile, s.StorageConfig.KeyFile)
if err != nil {
glog.Errorf("failed to load key pair while getting backends: %s", err)
} else {
tlsConfig.Certificates = []tls.Certificate{cert}
}
}
if len(s.StorageConfig.CAFile) > 0 {
if caCert, err := ioutil.ReadFile(s.StorageConfig.CAFile); err != nil {
glog.Errorf("failed to read ca file while getting backends: %s", err)
} else {
caPool := x509.NewCertPool()
caPool.AppendCertsFromPEM(caCert)
tlsConfig.RootCAs = caPool
tlsConfig.InsecureSkipVerify = false
}
}
backends := []Backend{}
for server := range servers {
backends = append(backends, Backend{
Server: server,
TLSConfig: tlsConfig,
})
}
return backends
}
func (s *DefaultStorageFactory) ResourcePrefix(groupResource schema.GroupResource) string {