Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validation webhook for service profiles #2623

Merged
merged 7 commits into from
Apr 5, 2019

Conversation

alpeb
Copy link
Member

@alpeb alpeb commented Apr 2, 2019

Fixes #2075

(This supersedes #2508 because it was easier to redo this in master than
rebasing that PR. It addresses all the feedback received in #2508)

The new webhook endpoint is created in a new container in the
linkerd-controller pod.

The proxy injector webhook was refactored to extract-out the scaffolding
to be reused by the validation webhook (and eventually by any other
admission webhooks we'd like to create in the future). The new common
stuff now reside under controller/webhook.

The validation webhook simply sets up the endpoint server and the
webhook config using pkg/webhook, and calls the old
profiles.Validate to perform the actual validation.

I Also got rid of the unused -no-init-container flag in the proxy injector.

Todo in a follow-up PRs: remove the SP check from the CLI check.

@alpeb alpeb self-assigned this Apr 2, 2019
@alpeb alpeb added the priority/P1 Planned for Release label Apr 2, 2019
@l5d-bot
Copy link
Collaborator

l5d-bot commented Apr 2, 2019

Integration test results for 956d8f3: success 🎉
Log output: https://gist.github.com/cd89e384c5564be90b0b410ea3851971

Copy link
Member

@siggy siggy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This webhook package is really slick! Most of my feedback is questions to help my own understanding of how this all works. Thanks!

cli/cmd/install.go Outdated Show resolved Hide resolved
controller/webhook/config.go Outdated Show resolved Hide resolved
controller/webhook/config.go Outdated Show resolved Hide resolved
controller/webhook/config.go Outdated Show resolved Hide resolved
}
return admissionReview
}
log.Infof("received admission review request %s", admissionReview.Request.UID)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider logging the namespace/name rather than UID as a more user-friendly message.

curiously when i turned on debug logging, i wasn't seeing the name in the debug output, any idea what's up?

$ bin/linkerd logs --control-plane-component controller --container sp-validator
...
linkerd linkerd-controller-c689f4fd9-hpqf6 sp-validator time="2019-04-03T01:22:47Z" level=debug msg="admission request: &AdmissionRequest{UID:fd3716f1-55ae-11e9-adbd-025000000001,Kind:linkerd.io/v1alpha1, Kind=ServiceProfile,Resource:linkerd.io/v1alpha1, Resource=serviceprofiles,SubResource:,Name:,Namespace:linkerd,Operation:CREATE,UserInfo:k8s_io_api_authentication_v1.UserInfo{Username:docker-for-desktop,UID:,Groups:[system:masters system:authenticated],Extra:map[string]ExtraValue{},},Object:k8s_io_apimachinery_pkg_runtime.RawExtension{Raw:*[123

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found that Request.Name is always sent empty by k8s. From their godoc:

// Name is the name of the object as presented in the request.  On a CREATE operation, the client may omit name and
// rely on the server to generate the name.  If that is the case, this method will return the empty string.
// +optional
Name string `json:"name,omitempty" protobuf:"bytes,5,opt,name=name"`

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😮

controller/webhook/launcher.go Outdated Show resolved Hide resolved
}
log.Infof("created webhook configuration: %s", selfLink)

s, err := NewServer(k8sAPI, *addr, config.WebhookServiceName, *controllerNamespace, rootCA, config.Handler)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if config.Handler only exists to get passed through Launch() into NewServer(), consider just making it a separate param on Launch(). similar question for MetricsPort and others.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kinda did that to avoid having to pass so many arguments, but I think I can reach a compromise in my next push 👍

controller/sp-validator/webhook_ops.go Outdated Show resolved Hide resolved
// Start starts the https server
func (s *Server) Start() {
log.Infof("listening at %s", s.Server.Addr)
if err := s.ListenAndServeTLS("", ""); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is ListenAndServeTLS eventually going to take params?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. The certs are added in s's setup above in the NewServer function.

@olix0r
Copy link
Member

olix0r commented Apr 3, 2019

What happens when --ha is used and multiple controller instances are spawned simultaneously?

@olix0r
Copy link
Member

olix0r commented Apr 3, 2019

I think our options are:

  1. Wait on this until Create Mutating Webhooks during install #2628 is done and proceed with this container in the controller pod.
  2. Create a new deploy with a new service account with a dedicated serviceaccount that has the ClusterRoleBinding to create service accounts. This deploy should not be scaled with HA.

@grampelberg
Copy link
Contributor

Create a new deploy with a new service account with a dedicated serviceaccount that has the ClusterRoleBinding to create service accounts. This deploy should not be scaled with HA.

I like this idea in general. The controller pod is getting a little large and hard to reason about. Are there any downsides?

@alpeb
Copy link
Member Author

alpeb commented Apr 3, 2019

@olix0r great catch 👍
The second option also sounds better to me. Of course #2628 will still have to be done.

@grampelberg
Copy link
Contributor

+1 to #2628 as well =)

Copy link
Contributor

@ihcsim ihcsim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts around the structure of the code. (I will review the webhook handlers separately, but looking good so far).

Also, instead of having the different proxy-injector and sp-validator packages with different webhook.go files and a separate webhook package, how about just put everything into the webhook package and name the different webhook files after their component? I see the case for a separate webhook package only if it's a lib in pkg.

Something like:

  • controller/webhook/proxy_injector.go
  • controller/webhook/proxy_injector_ops.go
  • controller/webhook/sp_validator.go
  • controller/webhook/sp_validator_ops.go
  • controller/webhook/tmpl

if err := s.Shutdown(); err != nil {
log.Error(err)
}
webhook.Launch(&webhook.Config{
Copy link
Contributor

@ihcsim ihcsim Apr 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it hard to follow (and sometimes write tests for) objects that are half-created. IIUC, the other half of the webhook.Config is in the launcher. I know there is a bit of a trade-off here, but personally, I'm ok with duplicating the flags code if it helps to make things clearer.

TIOLI: Along with my comment below on removing the ConfigOps interface and Ops struct, this part can look like:

// configure and parse flags code
// ...
config := NewMWC() // or config := NewVWC()
webhook.Launch(config)

)

// Ops satisfies the ConfigOps interface for managing MutatingWebhook configs
type Ops struct{}
Copy link
Contributor

@ihcsim ihcsim Apr 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIOLI: I wonder if we can reduce the amount of code by just creating a MWC and a VWC, and not have the ConfigOps interface and Ops struct.

What about this?

type MWC struct {
  // fields
}

func NewMWC(/* args */) *MWC { /* code */ }

func (m *MWC) Create() { /* code /* }
func (m *MWC) Get() { /* code */ }
func (m *MWC) Delete() { /* code */ }

And the same for the VWC.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we move all the code into one package as suggested above, we don't need an ConfigOps interface.

Copy link
Contributor

@ihcsim ihcsim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The webhook handlers LGTM. Just two comments on the admission response and VWC.

controller/sp-validator/webhook.go Outdated Show resolved Hide resolved
@ihcsim
Copy link
Contributor

ihcsim commented Apr 3, 2019

What happens when --ha is used and multiple controller instances are spawned simultaneously?

If we ever made the proxy injector a required component, we can consider having just one admission controller webhook (and rename the proxy injector to a more generic admission controller or something). The k8s repo has a similar example with one webhook, but multiple handlers.

@grampelberg
Copy link
Contributor

@ihcsim is there a reason to keep them separate right now? Should we write up an issue to fix that in the 2.4 cycle?

@ihcsim
Copy link
Contributor

ihcsim commented Apr 3, 2019

Right now, the proxy injector webhook is an optional component. AFAIK, this validating webhook is required. I can create an issue for it in 2.4.

@grampelberg
Copy link
Contributor

The webhook feels like a nice to have (though with minimal impact), I can go either way.

@alpeb
Copy link
Member Author

alpeb commented Apr 3, 2019

@ihcsim thanks for your reviews, I'm still going through them. One comment though:

how about just put everything into the webhook package and name the different webhook files after their component

If we go down the route of having separate deployments for the proxy-injector and the validator, it would make sense to keep them under different directories under /controller, don't you agree?

@ihcsim
Copy link
Contributor

ihcsim commented Apr 4, 2019

If we go down the route of having separate deployments for the proxy-injector and the validator, it would make sense to keep them under different directories under /controller

I don't think the kind of workloads we deploy to k8s should have any inference on the code packaging structure. Per stand-up, let's make this a TIOLI.

Copy link
Contributor

@klingerf klingerf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, this approach looks good to me overall, but I suspect it will change somewhat once you rebase against master and move the profile validator to a separate deployment. Will give it a more thorough look once that's pushed.

chart/templates/controller.yaml Outdated Show resolved Hide resolved
@alpeb alpeb force-pushed the alpeb/serviceprofile-validation2 branch from 956d8f3 to b7f7baf Compare April 5, 2019 01:35
@l5d-bot
Copy link
Collaborator

l5d-bot commented Apr 5, 2019

Integration test results for 2ecb804: fail 😕
Log output: https://gist.github.com/23b6cadc499a3d71241a8a1865a0a6de

@alpeb alpeb force-pushed the alpeb/serviceprofile-validation2 branch from 2ecb804 to 5998454 Compare April 5, 2019 01:52
@l5d-bot
Copy link
Collaborator

l5d-bot commented Apr 5, 2019

Integration test results for 5998454: success 🎉
Log output: https://gist.github.com/7a2c8b5697a5bae628db704f541cbbe1

Copy link
Contributor

@klingerf klingerf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update looks great! Just had a few bits of miscellaneous feedback, but otherwise looks good to me.

@@ -78,6 +78,7 @@ rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["list", "get", "watch"]
# TODO: this is for creating the webhook config. Can be removed when #2176 lands.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this comment belong on line 75, above the privs for "mutatingwebhookconfigurations"? This comment also gets printed in the install YAML that the CLI emits, so I'd call it semi-user-facing. I'm inclined to omit TODOs from our YAML files, unless you feel strongly that we need them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually belongs in both places. The pod listing ability is needed more precisely because of #2559.
I tried using Helm comments ({{- /* comment */ -}}) but for some reason that didn't fly. I'll remove anyways these comments and I'll make a note instead 😉

chart/templates/sp_validator.yaml Outdated Show resolved Hide resolved
chart/templates/sp_validator.yaml Show resolved Hide resolved
controller/webhook/launcher.go Outdated Show resolved Hide resolved
Copy link
Member

@siggy siggy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good! a few more comments...

Add sp-validator here (note that proxy-injector is added dynamically when --proxy-auto-inject is set):

linkerdSvcs = []string{
"linkerd-controller-api",
"linkerd-destination",
"linkerd-grafana",
"linkerd-identity",
"linkerd-prometheus",
"linkerd-web",
}
linkerdDeployReplicas = map[string]deploySpec{
"linkerd-controller": {1, []string{"destination", "public-api", "tap"}},
"linkerd-grafana": {1, []string{}},
"linkerd-identity": {1, []string{"identity"}},
"linkerd-prometheus": {1, []string{}},
"linkerd-web": {1, []string{"web"}},

controller/cmd/sp-validator/main.go Outdated Show resolved Hide resolved
controller/webhook/launcher.go Outdated Show resolved Hide resolved
controller/webhook/server.go Outdated Show resolved Hide resolved
}

if len(data) == 0 {
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this an error or some condition that should be logged?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's just a sanity check against empty payloads. I'll add some logging.

@alpeb alpeb force-pushed the alpeb/serviceprofile-validation2 branch from 2a6379e to d2acce5 Compare April 5, 2019 20:15
@alpeb
Copy link
Member Author

alpeb commented Apr 5, 2019

Alright, thanks for all the feedback 🙏
The latest push applies all the suggestions, and rebases against master.

Copy link
Contributor

@klingerf klingerf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⭐️ Looks great, thanks for making those updates!

alpeb added 7 commits April 5, 2019 15:54
Fixes #2075

(This supersedes #2508 because it was easier to redo this in master than
rebasing that PR. It addresses all the feedback received in #2508)

The new webhook endpoint is created in a new container in the
linkerd-controller pod.

The proxy injector webhook was refactored to extract-out the scaffolding
to be reused by the validation webhook (and eventually by any other
admission webhooks we'd like to create in the future). The new common
stuff now reside under controller/webhook.

The validation webhook simply sets up the endpoint server and the
webhook config using pkg/webhook, and calls the old
profiles.Validate to perform the actual validation.

I Also got rid of the unused -no-init-container flag in the proxy injector.

Todo in a follow-up PRs: remove the SP check from the CLI check.

Signed-off-by: Alejandro Pedraza <[email protected]>
Signed-off-by: Alejandro Pedraza <[email protected]>
Signed-off-by: Alejandro Pedraza <[email protected]>
Signed-off-by: Alejandro Pedraza <[email protected]>
Signed-off-by: Alejandro Pedraza <[email protected]>
Signed-off-by: Alejandro Pedraza <[email protected]>
@alpeb alpeb force-pushed the alpeb/serviceprofile-validation2 branch from 833a167 to bf35a14 Compare April 5, 2019 21:01
@alpeb alpeb merged commit edb2250 into master Apr 5, 2019
@alpeb alpeb deleted the alpeb/serviceprofile-validation2 branch April 5, 2019 21:10
@l5d-bot
Copy link
Collaborator

l5d-bot commented Apr 5, 2019

Integration test results for bf35a14: success 🎉
Log output: https://gist.github.com/7b1eee525cbce10e7aaada98a20bb8bd

KatherineMelnyk pushed a commit to KatherineMelnyk/linkerd2 that referenced this pull request Apr 5, 2019
Add validation webhook for service profiles

Fixes linkerd#2075

Todo in a follow-up PRs: remove the SP check from the CLI check.

Signed-off-by: Alejandro Pedraza <[email protected]>
Signed-off-by: [email protected] <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
priority/P1 Planned for Release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants