Skip to content

Commit

Permalink
docs: add KongServiceFacade docs and examples
Browse files Browse the repository at this point in the history
  • Loading branch information
czeslavo committed Dec 6, 2023
1 parent 9bbee3b commit 0049594
Show file tree
Hide file tree
Showing 11 changed files with 524 additions and 60 deletions.
82 changes: 82 additions & 0 deletions FEATURE_GATES.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,85 @@ services: if two HTTPRoute rules use both serviceA and serviceB in their
backendRefs, KIC will generate a single Kong service with endpoints from both
serviceA and serviceB, named for the first rule and match indices with that
combination of Services.

## Using KongServiceFacade

In KIC 3.1.0 we introduced a new feature called `KongServiceFacade`. Currently, we only
support `KongServiceFacade` to be used as a backend for `netv1.Ingress`es.
If you find this might be useful to you in other contexts (e.g. Gateway API's `HTTPRoute`s),
please let us know in the [#5216](https://github.com/Kong/kubernetes-ingress-controller/issues/5216)
issue tracking this effort.

### Installation

`KongServiceFacade` feature is currently in `Alpha` maturity and is disabled by default.
To start using it, you must not only enable the feature gate (`KongServiceFacade=true`),
but also install the `KongServiceFacade` CRD which is distributed in a separate
package we named `incubator`. You can install it by running:

```shell
kubectl apply -k github.com/Kong/kubernetes-ingress-controller/main/config/crd/incubator?ref=main
```

### Usage

Once the CRD is installed and the feature gate is set to `true`, you can start using it by creating
`KongServiceFacade` objects and use them as your `netv1.Ingress` backends. For example, to create
a `KongServiceFacade` that points to a service named `my-service` in the `default` namespace and uses
its port number `80`, you can run:

```shell
kubectl apply -f - <<EOF
apiVersion: incubator.konghq.com/v1alpha1
kind: KongServiceFacade
metadata:
name: my-service-facade
namespace: default
annotations:
kubernetes.io/ingress.class: kong
spec:
backendRef:
name: my-service
port: 80
EOF
```

To use the `KongServiceFacade` as a backend for your `netv1.Ingress`, you can create an `Ingress`
like the following:

```shell
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
namespace: default
annotations:
konghq.com/strip-path: "true"
spec:
ingressClassName: kong
rules:
- http:
paths:
- path: /my-service
pathType: Prefix
backend:
resource:
apiGroup: incubator.konghq.com
kind: KongServiceFacade
name: my-service-facade
```
Please note the `KongServiceFacade` must be in the same namespace as the `Ingress` that uses it.
An advantage of using `KongServiceFacade` over plain `corev1.Service`s is that you can create multiple
`KongServiceFacade`s that point to the same `Service` and KIC will always generate one Kong Service per each
`KongServiceFacade`. That enables you to, e.g., use different `KongPlugin`s for each `KongServiceFacade`
while still pointing to the same Kubernetes `Service`. A single `KongServiceFacade` may be used in multiple
`Ingress`es and customization done through the `KongServiceFacade`'s annotations will be honored in all of them
on a single Kong Service level (no need to duplicate annotations in multiple `Ingress`es).
For a complete example of using `KongServiceFacade` for customizing `Service` authentication methods, please
refer to the [kong-service-facade.yaml] manifest in our examples.
[kong-service-facade.yaml]: https://github.com/Kong/kubernetes-ingress-controller/blob/main/examples/kong-service-facade.yaml
211 changes: 211 additions & 0 deletions examples/kong-service-facade.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# This example demonstrates how to use the KongServiceFacade resource to
# configure Kong to route traffic to a Kubernetes Service, and secure it
# with a different plugin for each KongServiceFacade.
#
# To verify the example:
#
# 1. Install the KongServiceFacade CRD:
# $ kubectl apply -k "github.com/Kong/kubernetes-ingress-controller/config/crd/incubator?ref=main"
#
# 2. Install the Kong Ingress Controller with KongServiceFacade feature gate enabled:
# $ helm upgrade --install kong kong/ingress -n kong --create-namespace \
# --set controller.ingressController.env.feature_gates=KongServiceFacade=true \
# --set controller.ingressController.image.repository=kong/nightly-ingress-controller \
# --set controller.ingressController.image.tag=2023-12-06 \
# --set controller.ingressController.image.effectiveSemver=3.1.0
#
# 3. Apply the example manifest:
#
# $ kubectl apply -f "github.com/Kong/kubernetes-ingress-controller/examples/kong-service-facade.yaml?ref=main"
#
# 4. Get Proxy's Service external IP:
#
# $ export PROXY_IP=$(kubectl get service -n kong kong-gateway-proxy -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')
# $ echo $PROXY_IP # To ensure that the variable is set.
# 198.19.249.2
#
# 5. Verify that paths /alpha and /beta are secured with key-auth and basic-auth respectively (we're using httpie here):
#
# $ http -h ${PROXY_IP}/alpha key:alice-password # /alpha allows valid key.
# HTTP/1.1 200 OK
# $ http -h ${PROXY_IP}/alpha key:wrong-key # /alpha doesn't allow invalid key.
# HTTP/1.1 401 Unauthorized
# $ http -h ${PROXY_IP}/alpha -a bob:bob-password # /alpha doesn't allow valid basic-auth credentials.
# HTTP/1.1 401 Unauthorized
# $ http -h ${PROXY_IP}/beta -a bob:bob-password # /beta allows valid basic-auth credentials.
# HTTP/1.1 200 OK
# $ http -h ${PROXY_IP}/beta -a bob:wrong # /beta doesn't allow invalid basic-auth credentials.
# HTTP/1.1 401 Unauthorized
# $ http -h ${PROXY_IP}/beta key:alice-password # /beta doesn't allow valid key.
# HTTP/1.1 401 Unauthorized
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin-deployment
namespace: default
labels:
app: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
template:
metadata:
labels:
app: httpbin
spec:
containers:
- name: httpbin
image: kong/httpbin:0.1.0
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
labels:
app: httpbin
name: httpbin-deployment
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: httpbin
type: ClusterIP
---
# KongServiceFacade pointing to the httpbin-deployment,
# secured with key-auth plugin.
apiVersion: incubator.konghq.com/v1alpha1
kind: KongServiceFacade
metadata:
name: svc-facade-alpha
namespace: default
annotations:
kubernetes.io/ingress.class: kong
konghq.com/plugins: key-auth
spec:
backendRef:
name: httpbin-deployment
port: 80
---
# KongServiceFacade pointing to the same httpbin-deployment,
# secured with basic-auth plugin.
apiVersion: incubator.konghq.com/v1alpha1
kind: KongServiceFacade
metadata:
name: svc-facade-beta
namespace: default
annotations:
kubernetes.io/ingress.class: kong
konghq.com/plugins: basic-auth
spec:
backendRef:
name: httpbin-deployment
port: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: httpbin-ingress
namespace: default
annotations:
konghq.com/strip-path: "true"
spec:
ingressClassName: kong
rules:
- http:
paths:
- path: /alpha
pathType: Prefix
backend:
# The /alpha path uses the KongServiceFacade secured with key-auth plugin.
resource:
apiGroup: incubator.konghq.com
kind: KongServiceFacade
name: svc-facade-alpha
- path: /beta
pathType: Prefix
backend:
# The /beta path uses the KongServiceFacade secured with key-auth plugin.
resource:
apiGroup: incubator.konghq.com
kind: KongServiceFacade
name: svc-facade-beta
---
# The following resources are used to configure the key-auth and basic-auth plugins
# used by the KongServiceFacade resources.
#
# For key-auth it sets up a KongConsumer `alice` that can be authenticated by
# setting the `key` header to `alice-password`.
#
# For basic-auth it sets up a KongConsumer `bob` that can be authenticated by
# setting the `Authorization` header to `Basic <base64("bob:bob-password")>`.
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: key-auth
namespace: default
plugin: key-auth
config:
key_names: ["key"]
---
apiVersion: v1
kind: Secret
metadata:
name: alice-credentials
namespace: default
annotations:
kubernetes.io/ingress.class: kong
labels:
konghq.com/credential: key-auth
type: Opaque
stringData:
key: "alice-password"
---
apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
name: alice
namespace: default
annotations:
kubernetes.io/ingress.class: kong
username: alice
credentials:
- alice-credentials
---
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: basic-auth
namespace: default
plugin: basic-auth
---
apiVersion: v1
kind: Secret
metadata:
name: bob-credentials
namespace: default
annotations:
kubernetes.io/ingress.class: kong
labels:
konghq.com/credential: basic-auth
type: Opaque
stringData:
username: "bob"
password: "bob-password"
---
apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
name: bob
namespace: default
annotations:
kubernetes.io/ingress.class: kong
username: bob
credentials:
- bob-credentials
3 changes: 2 additions & 1 deletion test/e2e/all_in_one_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
k8stypes "k8s.io/apimachinery/pkg/types"

"github.com/kong/kubernetes-ingress-controller/v3/internal/metrics"
"github.com/kong/kubernetes-ingress-controller/v3/test/internal/helpers"
)

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -321,7 +322,7 @@ func TestDeployAllInOneDBLESS(t *testing.T) {

t.Log("restart the controller")
deployments.RestartController(ctx, t, env)
waitForDeploymentRollout(ctx, t, env, namespace, controllerDeploymentName)
helpers.WaitForDeploymentRollout(ctx, t, env.Cluster(), namespace, controllerDeploymentName)

t.Log("scale proxy to 3 replicas and verify that the new replica gets the old good configuration")
scaleDeployment(ctx, t, env, deployments.ProxyNN, 3)
Expand Down
54 changes: 4 additions & 50 deletions test/e2e/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,8 @@ func (d ManifestDeploy) Run(ctx context.Context, t *testing.T, env environments.
t.Log("waiting for controller to be ready")
deployments := getManifestDeployments(d.Path)

waitForDeploymentRollout(ctx, t, env, deployments.ControllerNN.Namespace, deployments.ControllerNN.Name)
waitForDeploymentRollout(ctx, t, env, deployments.ProxyNN.Namespace, deployments.ProxyNN.Name)
helpers.WaitForDeploymentRollout(ctx, t, env.Cluster(), deployments.ControllerNN.Namespace, deployments.ControllerNN.Name)
helpers.WaitForDeploymentRollout(ctx, t, env.Cluster(), deployments.ProxyNN.Namespace, deployments.ProxyNN.Name)

return deployments
}
Expand Down Expand Up @@ -312,52 +312,6 @@ func (d ManifestDeploy) Delete(ctx context.Context, t *testing.T, env environmen
require.NoError(t, err, string(out))
}

func waitForDeploymentRollout(ctx context.Context, t *testing.T, env environments.Environment, namespace, name string) {
require.Eventuallyf(t, func() bool {
deployment, err := env.Cluster().Client().AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return false
}

if err := allUpdatedReplicasRolledOutAndReady(deployment); err != nil {
t.Logf("%s/%s deployment not ready: %s", namespace, name, err)
return false
}

return true
}, kongComponentWait, time.Second, "deployment %s/%s didn't roll out in time", namespace, name)
}

// allUpdatedReplicasRolledOutAndReady ensures that all updated replicas are rolled out and ready. It is to make sure
// that the deployment rollout is finished and all the new replicas are ready to serve traffic before we proceed with
// the test. It returns an error with a reason if the deployment is not ready yet.
func allUpdatedReplicasRolledOutAndReady(d *appsv1.Deployment) error {
if newReplicasRolledOut := d.Spec.Replicas != nil && d.Status.UpdatedReplicas < *d.Spec.Replicas; newReplicasRolledOut {
return fmt.Errorf(
"%d out of %d new replicas have been updated",
d.Status.UpdatedReplicas,
*d.Spec.Replicas,
)
}

if oldReplicasPendingTermination := d.Status.Replicas > d.Status.UpdatedReplicas; oldReplicasPendingTermination {
return fmt.Errorf(
"%d old replicas pending termination",
d.Status.Replicas-d.Status.UpdatedReplicas,
)
}

if rolloutFinished := d.Status.AvailableReplicas == d.Status.UpdatedReplicas; !rolloutFinished {
return fmt.Errorf(
"%d of %d updated replicas are available",
d.Status.AvailableReplicas,
d.Status.UpdatedReplicas,
)
}

return nil
}

// Deployments represent the deployments that are deployed by the all-in-one manifests.
type Deployments struct {
ProxyNN k8stypes.NamespacedName
Expand Down Expand Up @@ -919,6 +873,6 @@ func (d Deployments) Restart(ctx context.Context, t *testing.T, env environments
})
require.NoError(t, err, "failed to delete proxy pods")

waitForDeploymentRollout(ctx, t, env, d.ControllerNN.Namespace, d.ControllerNN.Name)
waitForDeploymentRollout(ctx, t, env, d.ProxyNN.Namespace, d.ProxyNN.Name)
helpers.WaitForDeploymentRollout(ctx, t, env.Cluster(), d.ControllerNN.Namespace, d.ControllerNN.Name)
helpers.WaitForDeploymentRollout(ctx, t, env.Cluster(), d.ProxyNN.Namespace, d.ProxyNN.Name)
}
2 changes: 1 addition & 1 deletion test/e2e/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func testManifestsUpgrade(
variableName: "CONTROLLER_FEATURE_GATES",
value: featureGates,
}))
waitForDeploymentRollout(ctx, t, env, deployments.ControllerNN.Namespace, deployments.ControllerNN.Name)
helpers.WaitForDeploymentRollout(ctx, t, env.Cluster(), deployments.ControllerNN.Namespace, deployments.ControllerNN.Name)
}

t.Log("creating new ingress with new path /echo-new")
Expand Down
Loading

0 comments on commit 0049594

Please sign in to comment.