feat: add proxy --probe and enable lifecycle postStart hook (#490)
* feat: add proxy --probe and enable lifecycle postStart hook Signed-off-by: Anish Ramasekar <anish.ramasekar@gmail.com> * review feedback Signed-off-by: Anish Ramasekar <anish.ramasekar@gmail.com>
This commit is contained in:
Родитель
af2a905c1d
Коммит
e2bba608a9
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
proxyPort int
|
proxyPort int
|
||||||
|
probe bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -23,8 +24,21 @@ func main() {
|
||||||
logger.AddFlags()
|
logger.AddFlags()
|
||||||
|
|
||||||
flag.IntVar(&proxyPort, "proxy-port", 8000, "Port for the proxy to listen on")
|
flag.IntVar(&proxyPort, "proxy-port", 8000, "Port for the proxy to listen on")
|
||||||
|
flag.BoolVar(&probe, "probe", false, "Run a readyz probe on the proxy")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// when proxy is run with --probe, it will run a readyz probe on the proxy
|
||||||
|
// this is used in the postStart lifecycle hook to verify the proxy is ready
|
||||||
|
// to serve requests
|
||||||
|
if probe {
|
||||||
|
setupLog := logger.Get().WithName("probe")
|
||||||
|
if err := proxy.Probe(proxyPort); err != nil {
|
||||||
|
setupLog.Error(err, "failed to probe")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
setupLog := logger.Get().WithName("setup")
|
setupLog := logger.Get().WithName("setup")
|
||||||
p, err := proxy.NewProxy(proxyPort, logger.Get().WithName("proxy"))
|
p, err := proxy.NewProxy(proxyPort, logger.Get().WithName("proxy"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -370,13 +370,25 @@ func (dc *detectCmd) addProxyContainer(containers []corev1.Container) []corev1.C
|
||||||
Name: proxyContainerName,
|
Name: proxyContainerName,
|
||||||
Image: proxyImage,
|
Image: proxyImage,
|
||||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
Args: []string{"--log-encoder=json"},
|
Args: []string{
|
||||||
|
fmt.Sprintf("--proxy-port=%d", dc.proxyPort),
|
||||||
|
},
|
||||||
Ports: []corev1.ContainerPort{
|
Ports: []corev1.ContainerPort{
|
||||||
{
|
{
|
||||||
Name: "http",
|
|
||||||
ContainerPort: int32(dc.proxyPort),
|
ContainerPort: int32(dc.proxyPort),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Lifecycle: &corev1.Lifecycle{
|
||||||
|
PostStart: &corev1.LifecycleHandler{
|
||||||
|
Exec: &corev1.ExecAction{
|
||||||
|
Command: []string{
|
||||||
|
"/proxy",
|
||||||
|
fmt.Sprintf("--proxy-port=%d", dc.proxyPort),
|
||||||
|
"--probe",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
containers = append(containers, proxyContainer)
|
containers = append(containers, proxyContainer)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package podidentity
|
package podidentity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -48,13 +49,25 @@ var (
|
||||||
Name: proxyContainerName,
|
Name: proxyContainerName,
|
||||||
Image: proxyImage,
|
Image: proxyImage,
|
||||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
Args: []string{"--log-encoder=json"},
|
Args: []string{
|
||||||
|
fmt.Sprintf("--proxy-port=%d", 8000),
|
||||||
|
},
|
||||||
Ports: []corev1.ContainerPort{
|
Ports: []corev1.ContainerPort{
|
||||||
{
|
{
|
||||||
Name: "http",
|
|
||||||
ContainerPort: 8000,
|
ContainerPort: 8000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Lifecycle: &corev1.Lifecycle{
|
||||||
|
PostStart: &corev1.LifecycleHandler{
|
||||||
|
Exec: &corev1.ExecAction{
|
||||||
|
Command: []string{
|
||||||
|
"/proxy",
|
||||||
|
fmt.Sprintf("--proxy-port=%d", 8000),
|
||||||
|
"--probe",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// retryCount is the number of times to retry probing the proxy.
|
||||||
|
retryCount = 7
|
||||||
|
// waitTime is the time to wait between retries.
|
||||||
|
waitTime = time.Second
|
||||||
|
// clientTimeout is the timeout for the client.
|
||||||
|
clientTimeout = time.Second * 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// Probe checks if the proxy is ready to serve requests.
|
||||||
|
func Probe(port int) error {
|
||||||
|
url := fmt.Sprintf("http://%s:%d%s", localhost, port, readyzPathPrefix)
|
||||||
|
return probe(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func probe(url string) error {
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: clientTimeout,
|
||||||
|
}
|
||||||
|
for i := 0; i < retryCount; i++ {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
}
|
||||||
|
return errors.Errorf("failed to probe proxy")
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/klog/v2/klogr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProbe(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
p := &proxy{logger: klogr.New()}
|
||||||
|
rtr.PathPrefix("/readyz").HandlerFunc(p.readyzHandler)
|
||||||
|
|
||||||
|
if err := probe(server.URL + "/readyz"); err != nil {
|
||||||
|
t.Errorf("probe() = %v, want nil", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProbeError(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
if err := probe(server.URL + "/readyz"); err == nil {
|
||||||
|
t.Errorf("probe() = nil, want error")
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,10 +23,15 @@ const (
|
||||||
// "/metadata" portion is case-insensitive in IMDS
|
// "/metadata" portion is case-insensitive in IMDS
|
||||||
tokenPathPrefix = "/{type:(?i:metadata)}/identity/oauth2/token" // #nosec
|
tokenPathPrefix = "/{type:(?i:metadata)}/identity/oauth2/token" // #nosec
|
||||||
|
|
||||||
|
// readyzPathPrefix is the path for readiness probe
|
||||||
|
readyzPathPrefix = "/readyz"
|
||||||
|
|
||||||
// metadataIPAddress is the IP address of the metadata service
|
// metadataIPAddress is the IP address of the metadata service
|
||||||
metadataIPAddress = "169.254.169.254"
|
metadataIPAddress = "169.254.169.254"
|
||||||
// metadataPort is the port of the metadata service
|
// metadataPort is the port of the metadata service
|
||||||
metadataPort = 80
|
metadataPort = 80
|
||||||
|
// localhost is the hostname of the localhost
|
||||||
|
localhost = "localhost"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -85,10 +90,11 @@ func NewProxy(port int, logger logr.Logger) (Proxy, error) {
|
||||||
func (p *proxy) Run() error {
|
func (p *proxy) Run() error {
|
||||||
rtr := mux.NewRouter()
|
rtr := mux.NewRouter()
|
||||||
rtr.PathPrefix(tokenPathPrefix).HandlerFunc(p.msiHandler)
|
rtr.PathPrefix(tokenPathPrefix).HandlerFunc(p.msiHandler)
|
||||||
|
rtr.PathPrefix(readyzPathPrefix).HandlerFunc(p.readyzHandler)
|
||||||
rtr.PathPrefix("/").HandlerFunc(p.defaultPathHandler)
|
rtr.PathPrefix("/").HandlerFunc(p.defaultPathHandler)
|
||||||
|
|
||||||
p.logger.Info("starting the proxy server", "port", p.port, "userAgent", userAgent)
|
p.logger.Info("starting the proxy server", "port", p.port, "userAgent", userAgent)
|
||||||
return http.ListenAndServe(fmt.Sprintf("localhost:%d", p.port), rtr)
|
return http.ListenAndServe(fmt.Sprintf("%s:%d", localhost, p.port), rtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *proxy) msiHandler(w http.ResponseWriter, r *http.Request) {
|
func (p *proxy) msiHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -157,6 +163,11 @@ func (p *proxy) defaultPathHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
_, _ = w.Write(body)
|
_, _ = w.Write(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *proxy) readyzHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
p.logger.Info("received readyz request", "method", r.Method, "uri", r.RequestURI)
|
||||||
|
fmt.Fprintf(w, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
func doTokenRequest(ctx context.Context, clientID, resource, tenantID, authorityHost string) (*token, error) {
|
func doTokenRequest(ctx context.Context, clientID, resource, tenantID, authorityHost string) (*token, error) {
|
||||||
tokenFilePath := os.Getenv(webhook.AzureFederatedTokenFileEnvVar)
|
tokenFilePath := os.Getenv(webhook.AzureFederatedTokenFileEnvVar)
|
||||||
signedAssertion, err := readJWTFromFS(tokenFilePath)
|
signedAssertion, err := readJWTFromFS(tokenFilePath)
|
||||||
|
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Azure/azure-workload-identity/pkg/webhook"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"k8s.io/klog/v2/klogr"
|
"k8s.io/klog/v2/klogr"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-workload-identity/pkg/webhook"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -268,3 +268,43 @@ func testTokenHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
func testDefaultHandler(w http.ResponseWriter, r *http.Request) {
|
func testDefaultHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintf(w, "default_handler")
|
fmt.Fprintf(w, "default_handler")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProxy_ReadyZHandler(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
code int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "readyz",
|
||||||
|
path: "/readyz",
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "readyz",
|
||||||
|
path: "/readyz/",
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
p := &proxy{logger: klogr.New()}
|
||||||
|
rtr.PathPrefix("/readyz").HandlerFunc(p.readyzHandler)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, server.URL+test.path, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
rtr.ServeHTTP(recorder, req)
|
||||||
|
if recorder.Code != test.code {
|
||||||
|
t.Errorf("Expected code %d, got %d", test.code, recorder.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -254,6 +254,17 @@ func (m *podMutator) injectProxySidecarContainer(containers []corev1.Container,
|
||||||
Ports: []corev1.ContainerPort{{
|
Ports: []corev1.ContainerPort{{
|
||||||
ContainerPort: proxyPort,
|
ContainerPort: proxyPort,
|
||||||
}},
|
}},
|
||||||
|
Lifecycle: &corev1.Lifecycle{
|
||||||
|
PostStart: &corev1.LifecycleHandler{
|
||||||
|
Exec: &corev1.ExecAction{
|
||||||
|
Command: []string{
|
||||||
|
"/proxy",
|
||||||
|
fmt.Sprintf("--proxy-port=%d", proxyPort),
|
||||||
|
"--probe",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return containers
|
return containers
|
||||||
|
|
|
@ -1256,6 +1256,17 @@ func TestInjectProxySidecarContainer(t *testing.T) {
|
||||||
Ports: []corev1.ContainerPort{{
|
Ports: []corev1.ContainerPort{{
|
||||||
ContainerPort: proxyPort,
|
ContainerPort: proxyPort,
|
||||||
}},
|
}},
|
||||||
|
Lifecycle: &corev1.Lifecycle{
|
||||||
|
PostStart: &corev1.LifecycleHandler{
|
||||||
|
Exec: &corev1.ExecAction{
|
||||||
|
Command: []string{
|
||||||
|
"/proxy",
|
||||||
|
fmt.Sprintf("--proxy-port=%d", proxyPort),
|
||||||
|
"--probe",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
|
@ -94,7 +94,9 @@ test_helm_chart() {
|
||||||
--debug \
|
--debug \
|
||||||
-v=5
|
-v=5
|
||||||
poll_webhook_readiness
|
poll_webhook_readiness
|
||||||
make test-e2e-run
|
# TODO(aramase: remove GINKGO_SKIP once helm chart is updated to use v0.12.0 to include the
|
||||||
|
# new proxy probe feature.
|
||||||
|
GINKGO_SKIP=Proxy make test-e2e-run
|
||||||
|
|
||||||
${HELM} upgrade --install workload-identity-webhook "${REPO_ROOT}/manifest_staging/charts/workload-identity-webhook" \
|
${HELM} upgrade --install workload-identity-webhook "${REPO_ROOT}/manifest_staging/charts/workload-identity-webhook" \
|
||||||
--set image.repository="${REGISTRY:-mcr.microsoft.com/oss/azure/workload-identity/webhook}" \
|
--set image.repository="${REGISTRY:-mcr.microsoft.com/oss/azure/workload-identity/webhook}" \
|
||||||
|
|
|
@ -41,10 +41,7 @@ var _ = ginkgo.Describe("Proxy [LinuxOnly] [AKSSoakOnly] [Exclude:Arc]", func()
|
||||||
serviceAccount,
|
serviceAccount,
|
||||||
"mcr.microsoft.com/azure-cli",
|
"mcr.microsoft.com/azure-cli",
|
||||||
nil,
|
nil,
|
||||||
// az login -i reuses the connection, so we need to make sure the token request is made after the proxy sidecar is started
|
[]string{"/bin/sh", "-c", fmt.Sprintf("az login -i -u %s --allow-no-subscriptions --debug; sleep 3600", clientID)},
|
||||||
// otherwise the token request will fail. The sleep 15 is a workaround for the issue.
|
|
||||||
// TODO(aramase): remove the sleep after https://github.com/Azure/azure-workload-identity/issues/486 is fixed and v0.12.0 is released.
|
|
||||||
[]string{"/bin/sh", "-c", fmt.Sprintf("sleep 15; az login -i -u %s --allow-no-subscriptions --debug; sleep 3600", clientID)},
|
|
||||||
nil,
|
nil,
|
||||||
proxyAnnotations,
|
proxyAnnotations,
|
||||||
true,
|
true,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче