mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 09:49:50 +00:00
Merge pull request #49412 from bjhaid/etcd_healthz_endpoint
Automatic merge from submit-queue (batch tested with PRs 49989, 49806, 49649, 49412, 49512) This adds an etcd health check endpoint to kube-apiserver addressing https://github.com/kubernetes/kubernetes/issues/48215. **What this PR does / why we need it**: This ensures kube-apiserver `/healthz` endpoint fails whenever connectivity cannot be established to etcd, also ensures the etcd preflight checks works with unix sockets **Which issue this PR fixes**: fixes #48215 **Special notes for your reviewer**: This PR does not use the etcd client directly as the client object is wrapped behind the storage interface and not exposed directly for use, so I decided to reuse what's being done in the preflight. So this will only check fail for connectivity and not etcd auth related problems. I did not write tests for the endpoint because I couldn't find examples that I could follow for writing tests for healthz related endpoints, I'll be willing to write those tests if someone can point me at a relevant one. **Release note**: ```release-note Add etcd connectivity endpoint to healthz ``` @deads2k please help review, thanks!
This commit is contained in:
commit
22af024093
@ -18,7 +18,6 @@ go_library(
|
|||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kube-apiserver/app/options:go_default_library",
|
"//cmd/kube-apiserver/app/options:go_default_library",
|
||||||
"//cmd/kube-apiserver/app/preflight:go_default_library",
|
|
||||||
"//pkg/api:go_default_library",
|
"//pkg/api:go_default_library",
|
||||||
"//pkg/apis/apps:go_default_library",
|
"//pkg/apis/apps:go_default_library",
|
||||||
"//pkg/apis/batch:go_default_library",
|
"//pkg/apis/batch:go_default_library",
|
||||||
@ -95,6 +94,7 @@ go_library(
|
|||||||
"//vendor/k8s.io/apiserver/pkg/server/options:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/server/options:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/server/options/encryptionconfig:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/server/options/encryptionconfig:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/storage/etcd3/preflight:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
|
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
|
||||||
@ -118,7 +118,6 @@ filegroup(
|
|||||||
srcs = [
|
srcs = [
|
||||||
":package-srcs",
|
":package-srcs",
|
||||||
"//cmd/kube-apiserver/app/options:all-srcs",
|
"//cmd/kube-apiserver/app/options:all-srcs",
|
||||||
"//cmd/kube-apiserver/app/preflight:all-srcs",
|
|
||||||
"//cmd/kube-apiserver/app/testing:all-srcs",
|
"//cmd/kube-apiserver/app/testing:all-srcs",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
|
@ -54,11 +54,11 @@ import (
|
|||||||
//aggregatorinformers "k8s.io/kube-aggregator/pkg/client/informers/internalversion"
|
//aggregatorinformers "k8s.io/kube-aggregator/pkg/client/informers/internalversion"
|
||||||
openapi "k8s.io/kube-openapi/pkg/common"
|
openapi "k8s.io/kube-openapi/pkg/common"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/storage/etcd3/preflight"
|
||||||
clientgoinformers "k8s.io/client-go/informers"
|
clientgoinformers "k8s.io/client-go/informers"
|
||||||
clientgoclientset "k8s.io/client-go/kubernetes"
|
clientgoclientset "k8s.io/client-go/kubernetes"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
||||||
"k8s.io/kubernetes/cmd/kube-apiserver/app/preflight"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/apis/apps"
|
"k8s.io/kubernetes/pkg/apis/apps"
|
||||||
"k8s.io/kubernetes/pkg/apis/batch"
|
"k8s.io/kubernetes/pkg/apis/batch"
|
||||||
|
@ -62,7 +62,9 @@ go_library(
|
|||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/storage/etcd3/preflight:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
|
||||||
|
@ -18,6 +18,7 @@ package options
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
@ -26,7 +27,9 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||||
"k8s.io/apiserver/pkg/server"
|
"k8s.io/apiserver/pkg/server"
|
||||||
|
"k8s.io/apiserver/pkg/server/healthz"
|
||||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||||
|
"k8s.io/apiserver/pkg/storage/etcd3/preflight"
|
||||||
"k8s.io/apiserver/pkg/storage/storagebackend"
|
"k8s.io/apiserver/pkg/storage/storagebackend"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -127,15 +130,30 @@ func (s *EtcdOptions) AddFlags(fs *pflag.FlagSet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *EtcdOptions) ApplyTo(c *server.Config) error {
|
func (s *EtcdOptions) ApplyTo(c *server.Config) error {
|
||||||
|
s.addEtcdHealthEndpoint(c)
|
||||||
c.RESTOptionsGetter = &SimpleRestOptionsFactory{Options: *s}
|
c.RESTOptionsGetter = &SimpleRestOptionsFactory{Options: *s}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *EtcdOptions) ApplyWithStorageFactoryTo(factory serverstorage.StorageFactory, c *server.Config) error {
|
func (s *EtcdOptions) ApplyWithStorageFactoryTo(factory serverstorage.StorageFactory, c *server.Config) error {
|
||||||
|
s.addEtcdHealthEndpoint(c)
|
||||||
c.RESTOptionsGetter = &storageFactoryRestOptionsFactory{Options: *s, StorageFactory: factory}
|
c.RESTOptionsGetter = &storageFactoryRestOptionsFactory{Options: *s, StorageFactory: factory}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *EtcdOptions) addEtcdHealthEndpoint(c *server.Config) {
|
||||||
|
c.HealthzChecks = append(c.HealthzChecks, healthz.NamedCheck("etcd", func(r *http.Request) error {
|
||||||
|
done, err := preflight.EtcdConnection{ServerList: s.StorageConfig.ServerList}.CheckEtcdServers()
|
||||||
|
if !done {
|
||||||
|
return fmt.Errorf("etcd failed")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
type SimpleRestOptionsFactory struct {
|
type SimpleRestOptionsFactory struct {
|
||||||
Options EtcdOptions
|
Options EtcdOptions
|
||||||
}
|
}
|
||||||
|
@ -21,16 +21,3 @@ go_test(
|
|||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = ["//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library"],
|
deps = ["//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library"],
|
||||||
)
|
)
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
@ -36,20 +36,24 @@ type EtcdConnection struct {
|
|||||||
ServerList []string
|
ServerList []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (EtcdConnection) serverReachable(address string) bool {
|
func (EtcdConnection) serverReachable(connURL *url.URL) bool {
|
||||||
if conn, err := net.DialTimeout("tcp", address, connectionTimeout); err == nil {
|
scheme := connURL.Scheme
|
||||||
|
if scheme == "http" || scheme == "https" || scheme == "tcp" {
|
||||||
|
scheme = "tcp"
|
||||||
|
}
|
||||||
|
if conn, err := net.DialTimeout(scheme, connURL.Host, connectionTimeout); err == nil {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseServerURI(serverURI string) (string, error) {
|
func parseServerURI(serverURI string) (*url.URL, error) {
|
||||||
connURL, err := url.Parse(serverURI)
|
connURL, err := url.Parse(serverURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("unable to parse etcd url: %v", err)
|
return &url.URL{}, fmt.Errorf("unable to parse etcd url: %v", err)
|
||||||
}
|
}
|
||||||
return connURL.Host, nil
|
return connURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckEtcdServers will attempt to reach all etcd servers once. If any
|
// CheckEtcdServers will attempt to reach all etcd servers once. If any
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package preflight
|
package preflight
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -24,14 +25,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestParseServerURIGood(t *testing.T) {
|
func TestParseServerURIGood(t *testing.T) {
|
||||||
host, err := parseServerURI("https://127.0.0.1:2379")
|
connURL, err := parseServerURI("https://127.0.0.1:2379")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reference := "127.0.0.1:2379"
|
reference := "127.0.0.1:2379"
|
||||||
if host != reference {
|
if connURL.Host != reference {
|
||||||
t.Fatalf("server uri was not parsed correctly, host %s was invalid", host)
|
t.Fatalf("server uri was not parsed correctly, host %s was invalid", connURL.Host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseServerURIGoodUnix(t *testing.T) {
|
||||||
|
connURL, err := parseServerURI("unix://127.0.0.1:21002112605")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reference := "127.0.0.1:21002112605"
|
||||||
|
if connURL.Host != reference {
|
||||||
|
t.Fatalf("server uri was not parsed correctly, host %s was invalid", connURL.Host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +58,7 @@ func TestParseServerURIBad(t *testing.T) {
|
|||||||
func TestEtcdConnection(t *testing.T) {
|
func TestEtcdConnection(t *testing.T) {
|
||||||
etcd := new(EtcdConnection)
|
etcd := new(EtcdConnection)
|
||||||
|
|
||||||
result := etcd.serverReachable("-not a real network address-")
|
result := etcd.serverReachable(&url.URL{Host: "-not a real network address-", Scheme: "tcp"})
|
||||||
if result {
|
if result {
|
||||||
t.Fatal("checkConnection should not have succeeded")
|
t.Fatal("checkConnection should not have succeeded")
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user