Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/api-types/service-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ instead AWS Gateway API Controller uses its own version of the resource for the

ServiceExport resources can be targeted by [`TargetGroupPolicy`](target-group-policy.md) to configure protocol, protocol version, and health check settings. When a TargetGroupPolicy is applied to a ServiceExport, the configuration is automatically propagated to all target groups across all clusters that participate in the multi-cluster service mesh, ensuring consistent behavior regardless of which cluster contains the route resource.

**Protocol Override with ExportedPorts**: When using the `exportedPorts` field, TargetGroupPolicy protocol and protocolVersion settings will override the default protocol inferred from the `routeType`. For example, you can specify `routeType: HTTP` in the ServiceExport but use `protocol: HTTPS` in the TargetGroupPolicy to enable secure communication between VPC Lattice and your backend pods.

### Annotations (Legacy Method)

* `application-networking.k8s.aws/port`
Expand Down Expand Up @@ -115,3 +117,40 @@ spec:
```

This configuration ensures that all target groups created for the `inventory-service` across all clusters will use the same health check configuration, providing consistent health monitoring in multi-cluster deployments.

### Protocol Override Example

The following example demonstrates how to use TargetGroupPolicy to override the protocol for a ServiceExport with exportedPorts:

```yaml
# ServiceExport with HTTP routeType
apiVersion: application-networking.k8s.aws/v1alpha1
kind: ServiceExport
metadata:
name: secure-service
spec:
exportedPorts:
- port: 443
routeType: HTTP # Default would be HTTP/1
---
# TargetGroupPolicy overriding to HTTPS
apiVersion: application-networking.k8s.aws/v1alpha1
kind: TargetGroupPolicy
metadata:
name: https-override
spec:
targetRef:
group: "application-networking.k8s.aws"
kind: ServiceExport
name: secure-service
protocol: HTTPS # Overrides HTTP from routeType
protocolVersion: HTTP2 # Overrides HTTP1 default
healthCheck:
enabled: true
path: "/health"
protocol: HTTPS
protocolVersion: HTTP2
statusMatch: "200-299"
```

In this example, even though the ServiceExport specifies `routeType: HTTP`, the TargetGroupPolicy configures the target group to use HTTPS with HTTP/2, enabling secure communication between VPC Lattice and your backend pods.
36 changes: 36 additions & 0 deletions docs/api-types/target-group-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,42 @@ However, the policy will not take effect unless the target is valid.



### ServiceExport with ExportedPorts

When using ServiceExport with the `exportedPorts` field, TargetGroupPolicy protocol and protocolVersion settings will override the default protocol inferred from the `routeType`. This allows you to configure HTTPS or other protocols even when the routeType is HTTP.

Example:

```yaml
apiVersion: application-networking.k8s.aws/v1alpha1
kind: ServiceExport
metadata:
name: my-service
spec:
exportedPorts:
- port: 80
routeType: HTTP # Default would be HTTP/1
---
apiVersion: application-networking.k8s.aws/v1alpha1
kind: TargetGroupPolicy
metadata:
name: https-override
spec:
targetRef:
group: "application-networking.k8s.aws"
kind: ServiceExport
name: my-service
protocol: HTTPS # Overrides HTTP from routeType
protocolVersion: HTTP2 # Overrides HTTP1 default
healthCheck:
enabled: true
path: "/health"
protocol: HTTPS
protocolVersion: HTTP2
```

In this example, even though the ServiceExport specifies `routeType: HTTP`, the TargetGroupPolicy will configure the target group to use HTTPS with HTTP/2, providing secure communication between the VPC Lattice service and your backend pods.

### Limitations and Considerations

- Attaching TargetGroupPolicy to an existing Service that is already referenced by a route will result in a replacement
Expand Down
21 changes: 18 additions & 3 deletions pkg/gateway/model_build_targetgroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,13 @@ func (t *svcExportTargetGroupModelBuildTask) buildTargetGroupForExportedPort(ctx
return nil, err
}

// Get health check config from policy
_, _, healthCheckConfig, err := parseTargetGroupConfig(tgp)
// Get protocol, protocolVersion, and health check config from policy
policyProtocol, policyProtocolVersion, healthCheckConfig, err := parseTargetGroupConfig(tgp)
if err != nil {
return nil, err
}

// Set protocol and protocolVersion based on routeType
// Set default protocol and protocolVersion based on routeType
var protocol, protocolVersion string
switch exportedPort.RouteType {
case "HTTP":
Expand All @@ -210,6 +210,21 @@ func (t *svcExportTargetGroupModelBuildTask) buildTargetGroupForExportedPort(ctx
return nil, fmt.Errorf("unsupported route type: %s", exportedPort.RouteType)
}

// Override with TargetGroupPolicy settings if specified
if tgp != nil {
if tgp.Spec.Protocol != nil {
protocol = policyProtocol
}
// Only override protocolVersion if protocol is not TCP
if tgp.Spec.ProtocolVersion != nil && protocol != vpclattice.TargetGroupProtocolTcp {
protocolVersion = policyProtocolVersion
}
// For TCP protocol, ensure protocolVersion is empty
if protocol == vpclattice.TargetGroupProtocolTcp {
protocolVersion = ""
}
}

spec := model.TargetGroupSpec{
Type: model.TargetGroupTypeIP,
Port: exportedPort.Port,
Expand Down
251 changes: 251 additions & 0 deletions pkg/gateway/model_build_targetgroup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"

gwv1 "sigs.k8s.io/gateway-api/apis/v1"
gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"

mock_client "github.com/aws/aws-application-networking-k8s/mocks/controller-runtime/client"
"github.com/aws/aws-application-networking-k8s/pkg/config"
Expand Down Expand Up @@ -1274,3 +1275,253 @@ func Test_TGModelByHTTPRouteBuild_AdditionalTags(t *testing.T) {
})
}
}

func Test_TGModelByServiceExportWithExportedPorts_TargetGroupPolicy(t *testing.T) {
config.VpcID = "vpc-id"
config.ClusterName = "cluster-name"

tests := []struct {
name string
svcExport *anv1alpha1.ServiceExport
svc *corev1.Service
tgp *anv1alpha1.TargetGroupPolicy
wantErrIsNil bool
wantProtocol string
wantProtocolVersion string
wantHealthCheckProto *string
description string
}{
{
name: "ServiceExport with HTTP routeType and HTTPS protocol override from TargetGroupPolicy",
svcExport: &anv1alpha1.ServiceExport{
ObjectMeta: metav1.ObjectMeta{
Name: "https-override",
Namespace: "bar",
},
Spec: anv1alpha1.ServiceExportSpec{
ExportedPorts: []anv1alpha1.ExportedPort{
{
Port: 80,
RouteType: "HTTP",
},
},
},
},
svc: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "https-override",
Namespace: "bar",
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Port: 80},
},
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
},
},
tgp: &anv1alpha1.TargetGroupPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "https-override",
Namespace: "bar",
},
Spec: anv1alpha1.TargetGroupPolicySpec{
TargetRef: &gwv1alpha2.NamespacedPolicyTargetReference{
Group: "application-networking.k8s.aws",
Kind: "ServiceExport",
Name: "https-override",
},
Protocol: func() *string { s := vpclattice.TargetGroupProtocolHttps; return &s }(),
ProtocolVersion: func() *string { s := vpclattice.TargetGroupProtocolVersionHttp2; return &s }(),
HealthCheck: &anv1alpha1.HealthCheckConfig{
Enabled: func() *bool { b := true; return &b }(),
Protocol: func() *anv1alpha1.HealthCheckProtocol { p := anv1alpha1.HealthCheckProtocolHTTPS; return &p }(),
ProtocolVersion: func() *anv1alpha1.HealthCheckProtocolVersion {
v := anv1alpha1.HealthCheckProtocolVersionHTTP2
return &v
}(),
Port: func() *int64 { p := int64(80); return &p }(),
Path: func() *string { s := "/env"; return &s }(),
IntervalSeconds: func() *int64 { i := int64(10); return &i }(),
TimeoutSeconds: func() *int64 { t := int64(5); return &t }(),
HealthyThresholdCount: func() *int64 { c := int64(2); return &c }(),
UnhealthyThresholdCount: func() *int64 { c := int64(3); return &c }(),
StatusMatch: func() *string { s := "200-299"; return &s }(),
},
},
},
wantErrIsNil: true,
wantProtocol: vpclattice.TargetGroupProtocolHttps,
wantProtocolVersion: vpclattice.TargetGroupProtocolVersionHttp2,
wantHealthCheckProto: func() *string { s := vpclattice.TargetGroupProtocolHttps; return &s }(),
description: "should use HTTPS protocol and HTTP2 from TargetGroupPolicy when specified",
},
{
name: "ServiceExport with GRPC routeType and no TargetGroupPolicy override",
svcExport: &anv1alpha1.ServiceExport{
ObjectMeta: metav1.ObjectMeta{
Name: "grpc-default",
Namespace: "bar",
},
Spec: anv1alpha1.ServiceExportSpec{
ExportedPorts: []anv1alpha1.ExportedPort{
{
Port: 8081,
RouteType: "GRPC",
},
},
},
},
svc: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "grpc-default",
Namespace: "bar",
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Port: 8081},
},
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
},
},
wantErrIsNil: true,
wantProtocol: vpclattice.TargetGroupProtocolHttp,
wantProtocolVersion: vpclattice.TargetGroupProtocolVersionGrpc,
description: "should use default GRPC protocol when no TargetGroupPolicy",
},
{
name: "ServiceExport with TLS routeType and TCP protocol (no protocolVersion)",
svcExport: &anv1alpha1.ServiceExport{
ObjectMeta: metav1.ObjectMeta{
Name: "tls-tcp",
Namespace: "bar",
},
Spec: anv1alpha1.ServiceExportSpec{
ExportedPorts: []anv1alpha1.ExportedPort{
{
Port: 443,
RouteType: "TLS",
},
},
},
},
svc: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "tls-tcp",
Namespace: "bar",
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Port: 443},
},
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
},
},
tgp: &anv1alpha1.TargetGroupPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "tls-tcp",
Namespace: "bar",
},
Spec: anv1alpha1.TargetGroupPolicySpec{
TargetRef: &gwv1alpha2.NamespacedPolicyTargetReference{
Group: "application-networking.k8s.aws",
Kind: "ServiceExport",
Name: "tls-tcp",
},
Protocol: func() *string { s := vpclattice.TargetGroupProtocolTcp; return &s }(),
},
},
wantErrIsNil: true,
wantProtocol: vpclattice.TargetGroupProtocolTcp,
wantProtocolVersion: "",
description: "should use TCP protocol with empty protocolVersion",
},
{
name: "ServiceExport with HTTP routeType and HTTPS protocol with HTTP1 override",
svcExport: &anv1alpha1.ServiceExport{
ObjectMeta: metav1.ObjectMeta{
Name: "https-http1",
Namespace: "bar",
},
Spec: anv1alpha1.ServiceExportSpec{
ExportedPorts: []anv1alpha1.ExportedPort{
{
Port: 80,
RouteType: "HTTP",
},
},
},
},
svc: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "https-http1",
Namespace: "bar",
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Port: 80},
},
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
},
},
tgp: &anv1alpha1.TargetGroupPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "https-http1",
Namespace: "bar",
},
Spec: anv1alpha1.TargetGroupPolicySpec{
TargetRef: &gwv1alpha2.NamespacedPolicyTargetReference{
Group: "application-networking.k8s.aws",
Kind: "ServiceExport",
Name: "https-http1",
},
Protocol: func() *string { s := vpclattice.TargetGroupProtocolHttps; return &s }(),
ProtocolVersion: func() *string { s := vpclattice.TargetGroupProtocolVersionHttp1; return &s }(),
},
},
wantErrIsNil: true,
wantProtocol: vpclattice.TargetGroupProtocolHttps,
wantProtocolVersion: vpclattice.TargetGroupProtocolVersionHttp1,
description: "should use HTTPS protocol with HTTP1 from TargetGroupPolicy",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.TODO()

k8sSchema := runtime.NewScheme()
clientgoscheme.AddToScheme(k8sSchema)
anv1alpha1.Install(k8sSchema)
k8sClient := testclient.NewClientBuilder().WithScheme(k8sSchema).Build()

assert.NoError(t, k8sClient.Create(ctx, tt.svc.DeepCopy()))

if tt.tgp != nil {
assert.NoError(t, k8sClient.Create(ctx, tt.tgp.DeepCopy()))
}

builder := NewSvcExportTargetGroupBuilder(gwlog.FallbackLogger, k8sClient)

stack, err := builder.Build(ctx, tt.svcExport)
if !tt.wantErrIsNil {
assert.NotNil(t, err, tt.description)
return
}
assert.Nil(t, err, tt.description)

var targetGroups []*model.TargetGroup
err = stack.ListResources(&targetGroups)
assert.Nil(t, err)
assert.Equal(t, 1, len(targetGroups))

tg := targetGroups[0]
assert.Equal(t, tt.wantProtocol, tg.Spec.Protocol, tt.description)
assert.Equal(t, tt.wantProtocolVersion, tg.Spec.ProtocolVersion, tt.description)

if tt.wantHealthCheckProto != nil {
assert.NotNil(t, tg.Spec.HealthCheckConfig, tt.description)
assert.Equal(t, *tt.wantHealthCheckProto, *tg.Spec.HealthCheckConfig.Protocol, tt.description)
}
})
}
}
Loading
Loading