Commit b1040171 authored by zhouhaibing089's avatar zhouhaibing089 Committed by haibzhou

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).
parent dee81ed5
......@@ -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}
}
......
......@@ -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()
......
......@@ -50,17 +50,17 @@ type testResponse struct {
}
func NewTestREST(resp testResponse) *REST {
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"},
GetServersToValidate: func() map[string]*Server {
return map[string]*Server{
"test1": {Addr: "testserver1", Port: 8000, Path: "/healthz", Prober: prober},
}
},
prober: &fakeHttpProber{
result: resp.result,
body: resp.data,
err: resp.err,
},
}
}
......
......@@ -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
......
......@@ -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")
}
......
......@@ -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",
],
)
......
......@@ -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,
......
......@@ -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"}}
}
......@@ -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...)
}
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.List()
return backends
}
func (s *DefaultStorageFactory) ResourcePrefix(groupResource schema.GroupResource) string {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment