mirror of https://github.com/linkerd/linkerd2.git
Add opaque ports namespace inheritance to pods (#5941)
### What When a namespace has the opaque ports annotation, pods and services should inherit it if they do not have one themselves. Currently, services do this but pods do not. This can lead to surprising behavior where services are correctly marked as opaque, but pods are not. This changes the proxy-injector so that it now passes down the opaque ports annotation to pods from their namespace if they do not have their own annotation set. Closes #5736. ### How The proxy-injector webhook receives admission requests for pods and services. Regardless of the resource kind, it now checks if the resource should inherit the opaque ports annotation from its namespace. It should inherit it if the namespace has the annotation but the resource does not. If the resource should inherit the annotation, the webhook creates an annotation patch which is only responsible for adding the opaque ports annotation. After generating the annotation patch, it checks if the resource is injectable. From here there are a few scenarios: 1. If no annotation patch was created and the resource is not injectable, then admit the request with no changes. Examples of this are services with no OP annotation and inject-disabled pods with no OP annotation. 2. If the resource is a pod and it is injectable, create a patch that includes the proxy and proxy-init containers—as well as any other annotations and labels. 3. The above two scenarios lead to a patch being generated at this point, so no matter the resource the patch is returned. ### UI changes Resources are now reported to either be "injected", "skipped", or "annotated". The first pass at this PR worked around the fact that injection reports consider services and namespaces injectable. This is not accurate because they don't have pod templates that could be injected; they can however be annotated. To fix this, an injection report now considers resources "annotatable" and uses this to clean up some logic in the `inject` command, as well as avoid a more complex proxy-injector webhook. What's cool about this is it fixes some `inject` command output that would label resources as "injected" when they were not even mutated. For example, namespaces were always reported as being injected even if annotations were not added. Now, it will properly report that a namespace has been "annotated" or "skipped". ### Tests For testing, unit tests and integration tests have been added. Manual testing can be done by installing linkerd with `debug` controller log levels, and tailing the proxy-injector's app container when creating pods or services. Signed-off-by: Kevin Leimkuhler <kevin@kleimkuhler.com>
This commit is contained in:
parent
3e54bf9af9
commit
a11012819c
|
@ -29,7 +29,7 @@ const (
|
|||
hostNetworkDesc = "pods do not use host networking"
|
||||
sidecarDesc = "pods do not have a 3rd party proxy or initContainer already injected"
|
||||
injectDisabledDesc = "pods are not annotated to disable injection"
|
||||
unsupportedDesc = "at least one resource injected"
|
||||
unsupportedDesc = "at least one resource can be injected or annotated"
|
||||
udpDesc = "pod specs do not include UDP ports"
|
||||
automountServiceAccountTokenDesc = "pods do not have automountServiceAccountToken set to \"false\""
|
||||
slash = "/"
|
||||
|
@ -171,20 +171,25 @@ func (rt resourceTransformerInject) transform(bytes []byte) ([]byte, []inject.Re
|
|||
reports := []inject.Report{*report}
|
||||
|
||||
if conf.IsService() {
|
||||
opaquePortsAnnotations := map[string]string{}
|
||||
if opaquePorts, ok := rt.overrideAnnotations[k8s.ProxyOpaquePortsAnnotation]; ok {
|
||||
opaquePortsAnnotations[k8s.ProxyOpaquePortsAnnotation] = opaquePorts
|
||||
b, err := conf.AnnotateService(opaquePortsAnnotations)
|
||||
return b, reports, err
|
||||
opaquePorts, ok := rt.overrideAnnotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
if ok {
|
||||
annotations := map[string]string{k8s.ProxyOpaquePortsAnnotation: opaquePorts}
|
||||
bytes, err = conf.AnnotateService(annotations)
|
||||
report.Annotated = true
|
||||
}
|
||||
return bytes, reports, nil
|
||||
return bytes, reports, err
|
||||
}
|
||||
if rt.allowNsInject && conf.IsNamespace() {
|
||||
bytes, err = conf.AnnotateNamespace(rt.overrideAnnotations)
|
||||
report.Annotated = true
|
||||
return bytes, reports, err
|
||||
}
|
||||
if conf.HasPodTemplate() {
|
||||
conf.AppendPodAnnotations(rt.overrideAnnotations)
|
||||
report.Annotated = true
|
||||
}
|
||||
|
||||
if rt.allowNsInject && conf.IsNamespace() {
|
||||
b, err := conf.InjectNamespace(rt.overrideAnnotations)
|
||||
return b, reports, err
|
||||
}
|
||||
if b, _ := report.Injectable(); !b {
|
||||
if ok, _ := report.Injectable(); !ok {
|
||||
if errs := report.ThrowInjectError(); len(errs) > 0 {
|
||||
return bytes, reports, fmt.Errorf("failed to inject %s%s%s: %v", report.Kind, slash, report.Name, concatErrors(errs, ", "))
|
||||
}
|
||||
|
@ -201,10 +206,6 @@ func (rt resourceTransformerInject) transform(bytes []byte) ([]byte, []inject.Re
|
|||
conf.AppendPodAnnotation(k8s.ProxyInjectAnnotation, k8s.ProxyInjectEnabled)
|
||||
}
|
||||
|
||||
if len(rt.overrideAnnotations) > 0 {
|
||||
conf.AppendPodAnnotations(rt.overrideAnnotations)
|
||||
}
|
||||
|
||||
patchJSON, err := conf.GetPodPatch(rt.injectProxy)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -236,6 +237,7 @@ func (rt resourceTransformerInject) transform(bytes []byte) ([]byte, []inject.Re
|
|||
|
||||
func (resourceTransformerInject) generateReport(reports []inject.Report, output io.Writer) {
|
||||
injected := []inject.Report{}
|
||||
annotatable := false
|
||||
hostNetwork := []string{}
|
||||
sidecar := []string{}
|
||||
udp := []string{}
|
||||
|
@ -248,6 +250,10 @@ func (resourceTransformerInject) generateReport(reports []inject.Report, output
|
|||
injected = append(injected, r)
|
||||
}
|
||||
|
||||
if r.IsAnnotatable() {
|
||||
annotatable = true
|
||||
}
|
||||
|
||||
if r.HostNetwork {
|
||||
hostNetwork = append(hostNetwork, r.ResName())
|
||||
warningsPrinted = true
|
||||
|
@ -300,7 +306,7 @@ func (resourceTransformerInject) generateReport(reports []inject.Report, output
|
|||
output.Write([]byte(fmt.Sprintf("%s %s\n", okStatus, injectDisabledDesc)))
|
||||
}
|
||||
|
||||
if len(injected) == 0 {
|
||||
if len(injected) == 0 && !annotatable {
|
||||
output.Write([]byte(fmt.Sprintf("%s no supported objects found\n", warnStatus)))
|
||||
warningsPrinted = true
|
||||
} else if verbose {
|
||||
|
@ -329,9 +335,14 @@ func (resourceTransformerInject) generateReport(reports []inject.Report, output
|
|||
}
|
||||
|
||||
for _, r := range reports {
|
||||
if b, _ := r.Injectable(); b {
|
||||
if r.Annotated {
|
||||
output.Write([]byte(fmt.Sprintf("%s \"%s\" annotated\n", r.Kind, r.Name)))
|
||||
}
|
||||
ok, _ := r.Injectable()
|
||||
if ok {
|
||||
output.Write([]byte(fmt.Sprintf("%s \"%s\" injected\n", r.Kind, r.Name)))
|
||||
} else {
|
||||
}
|
||||
if !r.Annotated && !ok {
|
||||
if r.Kind != "" {
|
||||
output.Write([]byte(fmt.Sprintf("%s \"%s\" skipped\n", r.Kind, r.Name)))
|
||||
} else {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
@ -12,7 +12,7 @@ deployment "redis" injected
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
‼ "linkerd.io/inject: disabled" annotation set on deployment/web
|
||||
‼ no supported objects found
|
||||
|
||||
deployment "web" skipped
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
‼ "linkerd.io/inject: disabled" annotation set on deployment/web
|
||||
‼ no supported objects found
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
‼ deployment/web uses "protocol: UDP"
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
|
||||
namespace "emojivoto" injected
|
||||
namespace "emojivoto" skipped
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
namespace "emojivoto" injected
|
||||
namespace "emojivoto" skipped
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
√ pods do not use host networking
|
||||
√ pods do not have a 3rd party proxy or initContainer already injected
|
||||
√ pods are not annotated to disable injection
|
||||
√ at least one resource injected
|
||||
√ at least one resource can be injected or annotated
|
||||
√ pod specs do not include UDP ports
|
||||
√ pods do not have automountServiceAccountToken set to "false"
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/metadata/annotations",
|
||||
"value": {}
|
||||
},
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/metadata/annotations/config.linkerd.io~1opaque-ports",
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
namespace: kube-public
|
||||
annotations:
|
||||
config.linkerd.io/opaque-ports: "8080"
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
namespace: kube-public
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
|
@ -5,8 +5,6 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
"github.com/linkerd/linkerd2/controller/k8s"
|
||||
"github.com/linkerd/linkerd2/pkg/config"
|
||||
"github.com/linkerd/linkerd2/pkg/inject"
|
||||
|
@ -15,6 +13,7 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/record"
|
||||
)
|
||||
|
@ -34,35 +33,34 @@ func Inject(
|
|||
) (*admissionv1beta1.AdmissionResponse, error) {
|
||||
log.Debugf("request object bytes: %s", request.Object.Raw)
|
||||
|
||||
// Build the resource config based off the request metadata and kind of
|
||||
// object. This is later used to build the injection report and generated
|
||||
// patch.
|
||||
valuesConfig, err := config.Values(pkgK8s.MountPathValuesConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
namespace, err := api.NS().Lister().Get(request.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nsAnnotations := namespace.GetAnnotations()
|
||||
|
||||
resourceConfig := inject.NewResourceConfig(valuesConfig, inject.OriginWebhook).
|
||||
WithOwnerRetriever(ownerRetriever(ctx, api, request.Namespace)).
|
||||
WithNsAnnotations(nsAnnotations).
|
||||
WithKind(request.Kind.Kind)
|
||||
|
||||
// Build the injection report.
|
||||
report, err := resourceConfig.ParseMetaAndYAML(request.Object.Raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("received %s", report.ResName())
|
||||
|
||||
admissionResponse := &admissionv1beta1.AdmissionResponse{
|
||||
UID: request.UID,
|
||||
Allowed: true,
|
||||
}
|
||||
|
||||
configLabels := configToPrometheusLabels(resourceConfig)
|
||||
// If the resource has an owner, then it should be retrieved for recording
|
||||
// events.
|
||||
var parent *runtime.Object
|
||||
ownerKind := ""
|
||||
var ownerKind string
|
||||
if ownerRef := resourceConfig.GetOwnerRef(); ownerRef != nil {
|
||||
objs, err := api.GetObjects(request.Namespace, ownerRef.Kind, ownerRef.Name, labels.Everything())
|
||||
if err != nil {
|
||||
|
@ -74,9 +72,64 @@ func Inject(
|
|||
}
|
||||
ownerKind = strings.ToLower(ownerRef.Kind)
|
||||
}
|
||||
|
||||
configLabels := configToPrometheusLabels(resourceConfig)
|
||||
proxyInjectionAdmissionRequests.With(admissionRequestLabels(ownerKind, request.Namespace, report.InjectAnnotationAt, configLabels)).Inc()
|
||||
|
||||
if injectable, reasons := report.Injectable(); !injectable {
|
||||
// If the pod's namespace has the opaque ports annotation but the pod does
|
||||
// not, then it should be added to the pod template metadata.
|
||||
opaquePorts, opaquePortsOk := resourceConfig.GetOpaquePorts()
|
||||
if resourceConfig.IsPod() && opaquePortsOk {
|
||||
resourceConfig.AppendPodAnnotation(pkgK8s.ProxyOpaquePortsAnnotation, opaquePorts)
|
||||
}
|
||||
|
||||
// If the resource is injectable then admit it after creating a patch that
|
||||
// adds the proxy-init and proxy containers.
|
||||
injectable, reasons := report.Injectable()
|
||||
if injectable {
|
||||
resourceConfig.AppendPodAnnotation(pkgK8s.CreatedByAnnotation, fmt.Sprintf("linkerd/proxy-injector %s", version.Version))
|
||||
patchJSON, err := resourceConfig.GetPodPatch(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parent != nil {
|
||||
recorder.Event(*parent, v1.EventTypeNormal, eventTypeInjected, "Linkerd sidecar proxy injected")
|
||||
}
|
||||
log.Infof("injection patch generated for: %s", report.ResName())
|
||||
log.Debugf("injection patch: %s", patchJSON)
|
||||
proxyInjectionAdmissionResponses.With(admissionResponseLabels(ownerKind, request.Namespace, "false", "", report.InjectAnnotationAt, configLabels)).Inc()
|
||||
patchType := admissionv1beta1.PatchTypeJSONPatch
|
||||
return &admissionv1beta1.AdmissionResponse{
|
||||
UID: request.UID,
|
||||
Allowed: true,
|
||||
PatchType: &patchType,
|
||||
Patch: patchJSON,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// If the resource is not injectable but does need the opaque ports
|
||||
// annotation added, then admit it after creating a patch that adds the
|
||||
// annotation.
|
||||
if opaquePortsOk {
|
||||
patchJSON, err := resourceConfig.CreateAnnotationPatch(opaquePorts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("annotation patch generated for: %s", report.ResName())
|
||||
log.Debugf("annotation patch: %s", patchJSON)
|
||||
proxyInjectionAdmissionResponses.With(admissionResponseLabels(ownerKind, request.Namespace, "false", "", report.InjectAnnotationAt, configLabels)).Inc()
|
||||
patchType := admissionv1beta1.PatchTypeJSONPatch
|
||||
return &admissionv1beta1.AdmissionResponse{
|
||||
UID: request.UID,
|
||||
Allowed: true,
|
||||
PatchType: &patchType,
|
||||
Patch: patchJSON,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// The resource should be admitted without a patch. If it is a pod, create
|
||||
// an event to record that injection was skipped.
|
||||
if resourceConfig.IsPod() {
|
||||
var readableReasons, metricReasons string
|
||||
metricReasons = strings.Join(reasons, ",")
|
||||
for _, reason := range reasons {
|
||||
|
@ -89,37 +142,15 @@ func Inject(
|
|||
}
|
||||
log.Infof("skipped %s: %s", report.ResName(), readableReasons)
|
||||
proxyInjectionAdmissionResponses.With(admissionResponseLabels(ownerKind, request.Namespace, "true", metricReasons, report.InjectAnnotationAt, configLabels)).Inc()
|
||||
return admissionResponse, nil
|
||||
return &admissionv1beta1.AdmissionResponse{
|
||||
UID: request.UID,
|
||||
Allowed: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
resourceConfig.AppendPodAnnotations(map[string]string{
|
||||
pkgK8s.CreatedByAnnotation: fmt.Sprintf("linkerd/proxy-injector %s", version.Version),
|
||||
})
|
||||
var patchJSON []byte
|
||||
if resourceConfig.IsService() {
|
||||
patchJSON, err = resourceConfig.GetServicePatch()
|
||||
} else {
|
||||
patchJSON, err = resourceConfig.GetPodPatch(true)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(patchJSON) == 0 {
|
||||
return admissionResponse, nil
|
||||
}
|
||||
|
||||
if parent != nil {
|
||||
recorder.Event(*parent, v1.EventTypeNormal, eventTypeInjected, "Linkerd sidecar proxy injected")
|
||||
}
|
||||
log.Infof("patch generated for: %s", report.ResName())
|
||||
log.Debugf("patch: %s", patchJSON)
|
||||
proxyInjectionAdmissionResponses.With(admissionResponseLabels(ownerKind, request.Namespace, "false", "", report.InjectAnnotationAt, configLabels)).Inc()
|
||||
patchType := admissionv1beta1.PatchTypeJSONPatch
|
||||
admissionResponse.Patch = patchJSON
|
||||
admissionResponse.PatchType = &patchType
|
||||
|
||||
return admissionResponse, nil
|
||||
return &admissionv1beta1.AdmissionResponse{
|
||||
UID: request.UID,
|
||||
Allowed: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ownerRetriever(ctx context.Context, api *k8s.API, ns string) inject.OwnerRetrieverFunc {
|
||||
|
|
|
@ -190,7 +190,7 @@ func TestGetPodPatch(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestGetServicePatch(t *testing.T) {
|
||||
func TestGetAnnotationPatch(t *testing.T) {
|
||||
factory := fake.NewFactory(filepath.Join("fake", "data"))
|
||||
nsWithOpaquePorts, err := factory.Namespace("namespace-with-opaque-ports.yaml")
|
||||
if err != nil {
|
||||
|
@ -201,11 +201,19 @@ func TestGetServicePatch(t *testing.T) {
|
|||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
t.Run("by checking patch annotations", func(t *testing.T) {
|
||||
patchBytes, err := factory.FileContents("service.patch.json")
|
||||
servicePatchBytes, err := factory.FileContents("annotation.patch.json")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
patch, err := unmarshalPatch(patchBytes)
|
||||
servicePatch, err := unmarshalPatch(servicePatchBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
podPatchBytes, err := factory.FileContents("annotation.patch.json")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
podPatch, err := unmarshalPatch(podPatchBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
@ -222,8 +230,8 @@ func TestGetServicePatch(t *testing.T) {
|
|||
filename: "service-without-opaque-ports.yaml",
|
||||
ns: nsWithOpaquePorts,
|
||||
conf: confNsWithOpaquePorts(),
|
||||
expectedPatchBytes: patchBytes,
|
||||
expectedPatch: patch,
|
||||
expectedPatchBytes: servicePatchBytes,
|
||||
expectedPatch: servicePatch,
|
||||
},
|
||||
{
|
||||
name: "service with opaque ports and namespace with",
|
||||
|
@ -243,6 +251,26 @@ func TestGetServicePatch(t *testing.T) {
|
|||
ns: nsWithoutOpaquePorts,
|
||||
conf: confNsWithoutOpaquePorts(),
|
||||
},
|
||||
{
|
||||
name: "pod without opaque ports and namespace with",
|
||||
filename: "pod-without-opaque-ports.yaml",
|
||||
ns: nsWithOpaquePorts,
|
||||
conf: confNsWithOpaquePorts(),
|
||||
expectedPatchBytes: podPatchBytes,
|
||||
expectedPatch: podPatch,
|
||||
},
|
||||
{
|
||||
name: "pod with opaque ports and namespace with",
|
||||
filename: "pod-with-opaque-ports.yaml",
|
||||
ns: nsWithOpaquePorts,
|
||||
conf: confNsWithOpaquePorts(),
|
||||
},
|
||||
{
|
||||
name: "pod with opaque ports and namespace without",
|
||||
filename: "pod-with-opaque-ports.yaml",
|
||||
ns: nsWithoutOpaquePorts,
|
||||
conf: confNsWithoutOpaquePorts(),
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase // pin
|
||||
|
@ -259,9 +287,14 @@ func TestGetServicePatch(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
patchJSON, err := fullConf.GetServicePatch()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected PatchForAdmissionRequest error: %s", err)
|
||||
var patchJSON []byte
|
||||
opaquePorts, ok := fullConf.GetOpaquePorts()
|
||||
if ok {
|
||||
fullConf.AppendPodAnnotation(pkgK8s.ProxyOpaquePortsAnnotation, opaquePorts)
|
||||
patchJSON, err = fullConf.CreateAnnotationPatch(opaquePorts)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected PatchForAdmissionRequest error: %s", err)
|
||||
}
|
||||
}
|
||||
if len(testCase.expectedPatchBytes) != 0 && len(patchJSON) == 0 {
|
||||
t.Fatalf("There was no patch, but one was expected: %s", testCase.expectedPatchBytes)
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
package inject
|
||||
|
||||
var tpl = `[
|
||||
{{- if .AddRootAnnotations }}
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/metadata/annotations",
|
||||
"value": {}
|
||||
},
|
||||
{{- end }}
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/metadata/annotations/config.linkerd.io~1opaque-ports",
|
|
@ -90,12 +90,13 @@ type OwnerRetrieverFunc func(*corev1.Pod) (string, string)
|
|||
|
||||
// ResourceConfig contains the parsed information for a given workload
|
||||
type ResourceConfig struct {
|
||||
// These values used for the rendering of the patch may be further overridden
|
||||
// by the annotations on the resource or the resource's namespace.
|
||||
// These values used for the rendering of the patch may be further
|
||||
// overridden by the annotations on the resource or the resource's
|
||||
// namespace.
|
||||
values *l5dcharts.Values
|
||||
// These annotations from the resources's namespace are used as a base.
|
||||
// The resources's annotations will be applied on top of these, which allows
|
||||
// the nsAnnotations to act as a default.
|
||||
// The resources's annotations will be applied on top of these, which
|
||||
// allows the nsAnnotations to act as a default.
|
||||
nsAnnotations map[string]string
|
||||
ownerRetriever OwnerRetrieverFunc
|
||||
origin Origin
|
||||
|
@ -103,11 +104,9 @@ type ResourceConfig struct {
|
|||
workload struct {
|
||||
obj runtime.Object
|
||||
metaType metav1.TypeMeta
|
||||
|
||||
// Meta is the workload's metadata. It's exported so that metadata of
|
||||
// non-workload resources can be unmarshalled by the YAML parser
|
||||
Meta *metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
Meta *metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
ownerRef *metav1.OwnerReference
|
||||
}
|
||||
|
||||
|
@ -136,8 +135,9 @@ type podPatch struct {
|
|||
DebugContainer *l5dcharts.DebugContainer `json:"debugContainer"`
|
||||
}
|
||||
|
||||
type servicePatch struct {
|
||||
OpaquePorts string
|
||||
type annotationPatch struct {
|
||||
AddRootAnnotations bool
|
||||
OpaquePorts string
|
||||
}
|
||||
|
||||
// NewResourceConfig creates and initializes a ResourceConfig
|
||||
|
@ -148,6 +148,7 @@ func NewResourceConfig(values *l5dcharts.Values, origin Origin) *ResourceConfig
|
|||
origin: origin,
|
||||
}
|
||||
|
||||
config.workload.Meta = &metav1.ObjectMeta{}
|
||||
config.pod.meta = &metav1.ObjectMeta{}
|
||||
|
||||
// Values can be nil for commands like Uninject
|
||||
|
@ -305,24 +306,36 @@ func (conf *ResourceConfig) GetPodPatch(injectProxy bool) ([]byte, error) {
|
|||
return res, nil
|
||||
}
|
||||
|
||||
// GetServicePatch returns the JSON patch containing the service opaque ports
|
||||
// annotation if the annotation is present on the namespace, but absent on the
|
||||
// service.
|
||||
func (conf *ResourceConfig) GetServicePatch() ([]byte, error) {
|
||||
_, ok := conf.workload.Meta.Annotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
// There does not need to be a patch if the service already has the
|
||||
// annotation.
|
||||
// GetOpaquePorts returns two values. The first value is the the opaque ports
|
||||
// annotation value. The second is used to decide whether or not the caller
|
||||
// should add the annotation. The caller should not add the annotation if the
|
||||
// resource already has its own.
|
||||
func (conf *ResourceConfig) GetOpaquePorts() (string, bool) {
|
||||
_, ok := conf.pod.meta.Annotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
if ok {
|
||||
return nil, nil
|
||||
log.Debugf("using pod %s %s annotation value", conf.pod.meta.Name, k8s.ProxyOpaquePortsAnnotation)
|
||||
return "", false
|
||||
}
|
||||
opaquePorts, ok := conf.nsAnnotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
// There does not need to be a patch if the namespace does not have the
|
||||
// annotation.
|
||||
if !ok {
|
||||
return nil, nil
|
||||
_, ok = conf.workload.Meta.Annotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
if ok {
|
||||
log.Debugf("using service %s %s annotation value", conf.workload.Meta.Name, k8s.ProxyOpaquePortsAnnotation)
|
||||
return "", false
|
||||
}
|
||||
patch := &servicePatch{
|
||||
OpaquePorts: opaquePorts,
|
||||
annotation, ok := conf.nsAnnotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
if ok {
|
||||
log.Debugf("using namespace %s %s annotation value", conf.workload.Meta.Namespace, k8s.ProxyOpaquePortsAnnotation)
|
||||
return annotation, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// CreateAnnotationPatch returns a json patch which adds the opaque ports
|
||||
// annotation with the `opaquePorts` value.
|
||||
func (conf *ResourceConfig) CreateAnnotationPatch(opaquePorts string) ([]byte, error) {
|
||||
addRootAnnotations := len(conf.pod.meta.Annotations) == 0
|
||||
patch := &annotationPatch{
|
||||
AddRootAnnotations: addRootAnnotations,
|
||||
OpaquePorts: opaquePorts,
|
||||
}
|
||||
t, err := template.New("tpl").Parse(tpl)
|
||||
if err != nil {
|
||||
|
@ -484,7 +497,6 @@ func (conf *ResourceConfig) parse(bytes []byte) error {
|
|||
}
|
||||
conf.workload.obj = v
|
||||
conf.workload.Meta = &v.ObjectMeta
|
||||
// If annotations not present previously
|
||||
if conf.workload.Meta.Annotations == nil {
|
||||
conf.workload.Meta.Annotations = map[string]string{}
|
||||
}
|
||||
|
@ -528,6 +540,9 @@ func (conf *ResourceConfig) parse(bytes []byte) error {
|
|||
}
|
||||
}
|
||||
conf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace
|
||||
if conf.pod.meta.Annotations == nil {
|
||||
conf.pod.meta.Annotations = map[string]string{}
|
||||
}
|
||||
|
||||
case *corev1.Service:
|
||||
if err := yaml.Unmarshal(bytes, v); err != nil {
|
||||
|
@ -547,16 +562,15 @@ func (conf *ResourceConfig) parse(bytes []byte) error {
|
|||
}
|
||||
}
|
||||
|
||||
if conf.pod.meta.Annotations == nil {
|
||||
conf.pod.meta.Annotations = map[string]string{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conf *ResourceConfig) complete(template *corev1.PodTemplateSpec) {
|
||||
conf.pod.spec = &template.Spec
|
||||
conf.pod.meta = &template.ObjectMeta
|
||||
if conf.pod.meta.Annotations == nil {
|
||||
conf.pod.meta.Annotations = map[string]string{}
|
||||
}
|
||||
}
|
||||
|
||||
// injectPodSpec adds linkerd sidecars to the provided PodSpec.
|
||||
|
@ -921,20 +935,28 @@ func (conf *ResourceConfig) IsService() bool {
|
|||
return strings.ToLower(conf.workload.metaType.Kind) == k8s.Service
|
||||
}
|
||||
|
||||
//InjectNamespace annotates any given Namespace config
|
||||
func (conf *ResourceConfig) InjectNamespace(annotations map[string]string) ([]byte, error) {
|
||||
// IsPod checks if a given config is a workload of Kind pod.
|
||||
func (conf *ResourceConfig) IsPod() bool {
|
||||
return strings.ToLower(conf.workload.metaType.Kind) == k8s.Pod
|
||||
}
|
||||
|
||||
// HasPodTemplate checks if a given config has a pod template spec.
|
||||
func (conf *ResourceConfig) HasPodTemplate() bool {
|
||||
return conf.pod.meta != nil && conf.pod.spec != nil
|
||||
}
|
||||
|
||||
// AnnotateNamespace annotates a namespace resource config with `annotations`.
|
||||
func (conf *ResourceConfig) AnnotateNamespace(annotations map[string]string) ([]byte, error) {
|
||||
ns, ok := conf.workload.obj.(*corev1.Namespace)
|
||||
if !ok {
|
||||
return nil, errors.New("can't inject namespace. Type assertion failed")
|
||||
}
|
||||
ns.Annotations[k8s.ProxyInjectAnnotation] = k8s.ProxyInjectEnabled
|
||||
//For overriding annotations
|
||||
if len(annotations) > 0 {
|
||||
for annotation, value := range annotations {
|
||||
ns.Annotations[annotation] = value
|
||||
}
|
||||
}
|
||||
|
||||
j, err := getFilteredJSON(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -942,23 +964,18 @@ func (conf *ResourceConfig) InjectNamespace(annotations map[string]string) ([]by
|
|||
return yaml.JSONToYAML(j)
|
||||
}
|
||||
|
||||
// AnnotateService returns a Service with the appropriate annotations
|
||||
// Currently, a `Service` may only need the `config.linkerd.io/opaque-ports` annotation via `inject`
|
||||
// See - https://github.com/linkerd/linkerd2/pull/5721
|
||||
// AnnotateService annotates a service resource config with `annotations`.
|
||||
func (conf *ResourceConfig) AnnotateService(annotations map[string]string) ([]byte, error) {
|
||||
ns, ok := conf.workload.obj.(*corev1.Service)
|
||||
service, ok := conf.workload.obj.(*corev1.Service)
|
||||
if !ok {
|
||||
return nil, errors.New("can't inject service. Type assertion failed")
|
||||
}
|
||||
|
||||
//For overriding annotations
|
||||
if len(annotations) > 0 {
|
||||
for annotation, value := range annotations {
|
||||
ns.Annotations[annotation] = value
|
||||
service.Annotations[annotation] = value
|
||||
}
|
||||
}
|
||||
|
||||
j, err := getFilteredJSON(ns)
|
||||
j, err := getFilteredJSON(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -51,6 +51,8 @@ type Report struct {
|
|||
InjectDisabled bool
|
||||
InjectDisabledReason string
|
||||
InjectAnnotationAt string
|
||||
Annotatable bool
|
||||
Annotated bool
|
||||
AutomountServiceAccountToken bool
|
||||
|
||||
// Uninjected consists of two boolean flags to indicate if a proxy and
|
||||
|
@ -68,13 +70,13 @@ type Report struct {
|
|||
// from conf
|
||||
func newReport(conf *ResourceConfig) *Report {
|
||||
var name string
|
||||
if m := conf.workload.Meta; m != nil {
|
||||
name = m.Name
|
||||
} else if m := conf.pod.meta; m != nil {
|
||||
name = m.Name
|
||||
if conf.IsPod() {
|
||||
name = conf.pod.meta.Name
|
||||
if name == "" {
|
||||
name = m.GenerateName
|
||||
name = conf.pod.meta.GenerateName
|
||||
}
|
||||
} else if m := conf.workload.Meta; m != nil {
|
||||
name = m.Name
|
||||
}
|
||||
|
||||
report := &Report{
|
||||
|
@ -83,8 +85,8 @@ func newReport(conf *ResourceConfig) *Report {
|
|||
AutomountServiceAccountToken: true,
|
||||
}
|
||||
|
||||
if conf.pod.meta != nil && conf.pod.spec != nil {
|
||||
report.InjectDisabled, report.InjectDisabledReason, report.InjectAnnotationAt = report.disableByAnnotation(conf)
|
||||
if conf.HasPodTemplate() {
|
||||
report.InjectDisabled, report.InjectDisabledReason, report.InjectAnnotationAt = report.disabledByAnnotation(conf)
|
||||
report.HostNetwork = conf.pod.spec.HostNetwork
|
||||
report.Sidecar = healthcheck.HasExistingSidecars(conf.pod.spec)
|
||||
report.UDP = checkUDPPorts(conf.pod.spec)
|
||||
|
@ -96,10 +98,14 @@ func newReport(conf *ResourceConfig) *Report {
|
|||
report.AutomountServiceAccountToken = false
|
||||
}
|
||||
}
|
||||
} else if report.Kind != k8s.Service && report.Kind != k8s.Namespace {
|
||||
} else {
|
||||
report.UnsupportedResource = true
|
||||
}
|
||||
|
||||
if conf.HasPodTemplate() || conf.IsService() || conf.IsNamespace() {
|
||||
report.Annotatable = true
|
||||
}
|
||||
|
||||
return report
|
||||
}
|
||||
|
||||
|
@ -136,6 +142,11 @@ func (r *Report) Injectable() (bool, []string) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
// IsAnnotatable returns true if the resource for a report can be annotated.
|
||||
func (r *Report) IsAnnotatable() bool {
|
||||
return r.Annotatable
|
||||
}
|
||||
|
||||
func checkUDPPorts(t *v1.PodSpec) bool {
|
||||
// Check for ports with `protocol: UDP`, which will not be routed by Linkerd
|
||||
for _, container := range t.Containers {
|
||||
|
@ -148,9 +159,10 @@ func checkUDPPorts(t *v1.PodSpec) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// disabledByAnnotation checks annotations for both workload, namespace and returns
|
||||
// if disabled, Inject Disabled reason and the resource where that annotation was present
|
||||
func (r *Report) disableByAnnotation(conf *ResourceConfig) (bool, string, string) {
|
||||
// disabledByAnnotation checks the workload and namespace for the annotation
|
||||
// that disables injection. It returns if it is disabled, why it is disabled,
|
||||
// and the location where the annotation was present.
|
||||
func (r *Report) disabledByAnnotation(conf *ResourceConfig) (bool, string, string) {
|
||||
// truth table of the effects of the inject annotation:
|
||||
//
|
||||
// origin | namespace | pod | inject? | return
|
||||
|
|
|
@ -384,7 +384,7 @@ func TestDisableByAnnotation(t *testing.T) {
|
|||
resourceConfig.pod.spec = &corev1.PodSpec{} //initialize empty spec to prevent test from failing
|
||||
|
||||
report := newReport(resourceConfig)
|
||||
if actual, _, _ := report.disableByAnnotation(resourceConfig); testCase.expected != actual {
|
||||
if actual, _, _ := report.disabledByAnnotation(resourceConfig); testCase.expected != actual {
|
||||
t.Errorf("Expected %t. Actual %t", testCase.expected, actual)
|
||||
}
|
||||
})
|
||||
|
@ -426,7 +426,7 @@ func TestDisableByAnnotation(t *testing.T) {
|
|||
resourceConfig.pod.spec = &corev1.PodSpec{} //initialize empty spec to prevent test from failing
|
||||
|
||||
report := newReport(resourceConfig)
|
||||
if actual, _, _ := report.disableByAnnotation(resourceConfig); testCase.expected != actual {
|
||||
if actual, _, _ := report.disabledByAnnotation(resourceConfig); testCase.expected != actual {
|
||||
t.Errorf("Expected %t. Actual %t", testCase.expected, actual)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -19,6 +19,11 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
)
|
||||
|
||||
const (
|
||||
opaquePorts = "11211"
|
||||
manualOpaquePorts = "22122"
|
||||
)
|
||||
|
||||
//////////////////////
|
||||
/// TEST SETUP ///
|
||||
//////////////////////
|
||||
|
@ -358,7 +363,7 @@ func TestInjectAutoAnnotationPermutations(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInjectAutoPod(t *testing.T) {
|
||||
podYAML, err := testutil.ReadFile("testdata/pod.yaml")
|
||||
podsYAML, err := testutil.ReadFile("testdata/pods.yaml")
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to read inject test file",
|
||||
"failed to read inject test file: %s", err)
|
||||
|
@ -366,8 +371,10 @@ func TestInjectAutoPod(t *testing.T) {
|
|||
|
||||
injectNS := "inject-pod-test"
|
||||
podName := "inject-pod-test-terminus"
|
||||
opaquePodName := "inject-opaque-pod-test-terminus"
|
||||
nsAnnotations := map[string]string{
|
||||
k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
|
||||
k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
|
||||
k8s.ProxyOpaquePortsAnnotation: opaquePorts,
|
||||
}
|
||||
|
||||
truthy := true
|
||||
|
@ -422,10 +429,10 @@ func TestInjectAutoPod(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
TestHelper.WithDataPlaneNamespace(ctx, injectNS, nsAnnotations, t, func(t *testing.T, ns string) {
|
||||
o, err := TestHelper.Kubectl(podYAML, "--namespace", ns, "create", "-f", "-")
|
||||
o, err := TestHelper.Kubectl(podsYAML, "--namespace", ns, "create", "-f", "-")
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to create pod",
|
||||
"failed to create pod/%s in namespace %s for %s: %s", podName, ns, err, o)
|
||||
testutil.AnnotatedFatalf(t, "failed to create pods",
|
||||
"failed to create pods in namespace %s for %s: %s", ns, err, o)
|
||||
}
|
||||
|
||||
o, err = TestHelper.Kubectl("", "--namespace", ns, "wait", "--for=condition=initialized", "--timeout=120s", "pod/"+podName)
|
||||
|
@ -434,6 +441,7 @@ func TestInjectAutoPod(t *testing.T) {
|
|||
"failed to wait for condition=initialized for pod/%s in namespace %s: %s: %s", podName, ns, err, o)
|
||||
}
|
||||
|
||||
// Check that pods with no annotation inherit from the namespace.
|
||||
pods, err := TestHelper.GetPods(ctx, ns, map[string]string{"app": podName})
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to get pods", "failed to get pods for namespace %s: %s", ns, err)
|
||||
|
@ -441,6 +449,27 @@ func TestInjectAutoPod(t *testing.T) {
|
|||
if len(pods) != 1 {
|
||||
testutil.Fatalf(t, "wrong number of pods returned for namespace %s: %d", ns, len(pods))
|
||||
}
|
||||
annotation, ok := pods[0].Annotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
if !ok {
|
||||
testutil.Fatalf(t, "pod in namespace %s did not inherit opaque ports annotation", ns)
|
||||
}
|
||||
if annotation != opaquePorts {
|
||||
testutil.Fatalf(t, "expected pod in namespace %s to have %s opaque ports, but it had %s", ns, opaquePorts, annotation)
|
||||
}
|
||||
|
||||
// Check that pods with an annotation do not inherit from the
|
||||
// namespace.
|
||||
opaquePods, err := TestHelper.GetPods(ctx, ns, map[string]string{"app": opaquePodName})
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to get pods", "failed to get pods for namespace %s: %s", ns, err)
|
||||
}
|
||||
if len(opaquePods) != 1 {
|
||||
testutil.Fatalf(t, "wrong number of pods returned for namespace %s: %d", ns, len(opaquePods))
|
||||
}
|
||||
annotation = opaquePods[0].Annotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
if annotation != manualOpaquePorts {
|
||||
testutil.Fatalf(t, "expected pod in namespace %s to have %s opaque ports, but it had %s", ns, manualOpaquePorts, annotation)
|
||||
}
|
||||
|
||||
containers := pods[0].Spec.Containers
|
||||
if proxyContainer := testutil.GetProxyContainer(containers); proxyContainer == nil {
|
||||
|
@ -464,3 +493,115 @@ func TestInjectAutoPod(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInjectDisabledAutoPod(t *testing.T) {
|
||||
podsYAML, err := testutil.ReadFile("testdata/pods.yaml")
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to read inject test file",
|
||||
"failed to read inject test file: %s", err)
|
||||
}
|
||||
|
||||
ns := "inject-disabled-pod-test"
|
||||
podName := "inject-pod-test-terminus"
|
||||
opaquePodName := "inject-opaque-pod-test-terminus"
|
||||
nsAnnotations := map[string]string{
|
||||
k8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,
|
||||
k8s.ProxyOpaquePortsAnnotation: opaquePorts,
|
||||
}
|
||||
ctx := context.Background()
|
||||
TestHelper.WithDataPlaneNamespace(ctx, ns, nsAnnotations, t, func(t *testing.T, ns string) {
|
||||
o, err := TestHelper.Kubectl(podsYAML, "--namespace", ns, "create", "-f", "-")
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to create pods",
|
||||
"failed to create pods in namespace %s for %s: %s", ns, err, o)
|
||||
}
|
||||
|
||||
o, err = TestHelper.Kubectl("", "--namespace", ns, "wait", "--for=condition=initialized", "--timeout=120s", "pod/"+podName)
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to wait for condition=initialized",
|
||||
"failed to wait for condition=initialized for pod/%s in namespace %s: %s: %s", podName, ns, err, o)
|
||||
}
|
||||
|
||||
// Check that pods with no annotation inherit from the namespace.
|
||||
pods, err := TestHelper.GetPods(ctx, ns, map[string]string{"app": podName})
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to get pods", "failed to get pods for namespace %s: %s", ns, err)
|
||||
}
|
||||
if len(pods) != 1 {
|
||||
testutil.Fatalf(t, "wrong number of pods returned for namespace %s: %d", ns, len(pods))
|
||||
}
|
||||
annotation, ok := pods[0].Annotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
if !ok {
|
||||
testutil.Fatalf(t, "pod in namespace %s did not inherit opaque ports annotation", ns)
|
||||
}
|
||||
if annotation != opaquePorts {
|
||||
testutil.Fatalf(t, "expected pod in namespace %s to have %s opaque ports, but it had %s", ns, opaquePorts, annotation)
|
||||
}
|
||||
|
||||
// Check that pods with an annotation do not inherit from the
|
||||
// namespace.
|
||||
opaquePods, err := TestHelper.GetPods(ctx, ns, map[string]string{"app": opaquePodName})
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to get pods", "failed to get pods for namespace %s: %s", ns, err)
|
||||
}
|
||||
if len(opaquePods) != 1 {
|
||||
testutil.Fatalf(t, "wrong number of pods returned for namespace %s: %d", ns, len(opaquePods))
|
||||
}
|
||||
annotation = opaquePods[0].Annotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
if annotation != manualOpaquePorts {
|
||||
testutil.Fatalf(t, "expected pod in namespace %s to have %s opaque ports, but it had %s", ns, manualOpaquePorts, annotation)
|
||||
}
|
||||
|
||||
containers := pods[0].Spec.Containers
|
||||
if proxyContainer := testutil.GetProxyContainer(containers); proxyContainer != nil {
|
||||
testutil.Fatalf(t, "pod in namespace %s should not have been injected", ns)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInjectService(t *testing.T) {
|
||||
servicesYAML, err := testutil.ReadFile("testdata/services.yaml")
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to read inject test file",
|
||||
"failed to read inject test file: %s", err)
|
||||
}
|
||||
|
||||
ns := "inject-service-test"
|
||||
serviceName := "service-test"
|
||||
opaqueServiceName := "opaque-service-test"
|
||||
nsAnnotations := map[string]string{
|
||||
k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
|
||||
k8s.ProxyOpaquePortsAnnotation: opaquePorts,
|
||||
}
|
||||
ctx := context.Background()
|
||||
TestHelper.WithDataPlaneNamespace(ctx, ns, nsAnnotations, t, func(t *testing.T, ns string) {
|
||||
o, err := TestHelper.Kubectl(servicesYAML, "--namespace", ns, "create", "-f", "-")
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to create services",
|
||||
"failed to create services in namespace %s for %s: %s", ns, err, o)
|
||||
}
|
||||
|
||||
// Check that the service with no annotation inherits from the namespace.
|
||||
service, err := TestHelper.GetService(ctx, ns, serviceName)
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to get service", "failed to get service for namespace %s: %s", ns, err)
|
||||
}
|
||||
annotation, ok := service.Annotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
if !ok {
|
||||
testutil.Fatalf(t, "pod in namespace %s did not inherit opaque ports annotation", ns)
|
||||
}
|
||||
if annotation != opaquePorts {
|
||||
testutil.Fatalf(t, "expected pod in namespace %s to have %s opaque ports, but it had %s", ns, opaquePorts, annotation)
|
||||
}
|
||||
|
||||
// Check that the service with no annotation did not inherit from the namespace.
|
||||
service, err = TestHelper.GetService(ctx, ns, opaqueServiceName)
|
||||
if err != nil {
|
||||
testutil.AnnotatedFatalf(t, "failed to get service", "failed to get service for namespace %s: %s", ns, err)
|
||||
}
|
||||
annotation = service.Annotations[k8s.ProxyOpaquePortsAnnotation]
|
||||
if annotation != manualOpaquePorts {
|
||||
testutil.Fatalf(t, "expected service in namespace %s to have %s opaque ports, but it had %s", ns, manualOpaquePorts, annotation)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: inject-pod-test-terminus
|
||||
labels:
|
||||
app: inject-pod-test-terminus
|
||||
spec:
|
||||
containers:
|
||||
- name: bb-terminus
|
||||
image: buoyantio/bb:v0.0.6
|
||||
args: ["terminus", "--grpc-server-port", "9090", "--response-text", "BANANA"]
|
||||
ports:
|
||||
- containerPort: 9090
|
|
@ -0,0 +1,29 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: inject-pod-test-terminus
|
||||
labels:
|
||||
app: inject-pod-test-terminus
|
||||
spec:
|
||||
containers:
|
||||
- name: bb-terminus
|
||||
image: buoyantio/bb:v0.0.6
|
||||
args: ["terminus", "--grpc-server-port", "9090", "--response-text", "BANANA"]
|
||||
ports:
|
||||
- containerPort: 9090
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: inject-opaque-pod-test-terminus
|
||||
annotations:
|
||||
config.linkerd.io/opaque-ports: "22122"
|
||||
labels:
|
||||
app: inject-opaque-pod-test-terminus
|
||||
spec:
|
||||
containers:
|
||||
- name: bb-terminus
|
||||
image: buoyantio/bb:v0.0.6
|
||||
args: ["terminus", "--grpc-server-port", "9090", "--response-text", "BANANA"]
|
||||
ports:
|
||||
- containerPort: 9090
|
|
@ -0,0 +1,24 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service-test
|
||||
spec:
|
||||
selector:
|
||||
app: svc
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations:
|
||||
config.linkerd.io/opaque-ports: "22122"
|
||||
name: opaque-service-test
|
||||
spec:
|
||||
selector:
|
||||
app: svc
|
||||
ports:
|
||||
- port: 22122
|
||||
targetPort: 22122
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
deployment "smoke-test-terminus" injected
|
||||
service "smoke-test-terminus-svc" injected
|
||||
service "smoke-test-terminus-svc" skipped
|
||||
deployment "smoke-test-gateway" injected
|
||||
service "smoke-test-gateway-svc" injected
|
||||
service "smoke-test-gateway-svc" skipped
|
||||
|
||||
|
|
|
@ -242,6 +242,15 @@ func (h *KubernetesHelper) CheckService(ctx context.Context, namespace string, s
|
|||
})
|
||||
}
|
||||
|
||||
// GetService gets a service that exists in a namespace.
|
||||
func (h *KubernetesHelper) GetService(ctx context.Context, namespace string, serviceName string) (*corev1.Service, error) {
|
||||
service, err := h.clientset.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
// GetPods returns all pods with the given labels
|
||||
func (h *KubernetesHelper) GetPods(ctx context.Context, namespace string, podLabels map[string]string) ([]corev1.Pod, error) {
|
||||
podList, err := h.clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
|
||||
|
|
Loading…
Reference in New Issue