Skip to main content

Expose a Service

In this guide, you'll learn how to expose a service with Holos using the Gateway API.

The Concepts page defines capitalized terms such as Platform and Component.

What you'll need

You'll need the following tools installed to complete this guide.

  1. holos - to build the Platform.
  2. helm - to render Helm Components.
  3. kubectl - to render Kustomize Components.

As an optional, but recommended step, complete the Local Cluster guide if you'd like to apply the rendered manifests to a cluster. This will smooth out the friction of managing certificates.

Create a Git Repository

Start by initializing an empty Git repository. Holos operates on local files stored in a Git repository.

mkdir expose-a-service
cd expose-a-service
git init

This guide assumes you will run commands from the root directory of the Git repository unless stated otherwise.

Generate the Platform

Generate a platform with one workload cluster. The guide Platform is intended as a starting point for all of our guides.

holos generate platform guide
holos generate component workload-cluster

Commit the generated platform config to the repository.

git add .
git commit -m "holos generate platform guide - $(holos --version)"

Namespaces

We often need to manage namespaces prior to workloads being deployed. This is necessary because a namespace is a security boundary. Holos makes it easier, safer, and more consistent to manage service accounts, role bindings, and secrets prior to deploying workloads into a namespace.

We'll see how this works with the namespaces component, which offers a mechanism for other components to register their namespaces. The namespaces component initializes each registered namespace, optionally mixing in resources consistently.

Run the following command to generate the namespaces component.

holos generate component namespaces

The command generates two main configuration files like we've seen with other components. One file at the leaf, and another at the root. The leaf uses a Kubernetes build plan to produce resources directly from CUE.

components/namespaces/namespaces.cue

package holos

let Objects = {
Name: "namespaces"
Resources: Namespace: #Namespaces
}

// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output

Notice the highlighted line in the leaf file. Resources are managed directly in CUE at the leaf using the Kubernetes component. This is the same mechanism used to mix-in resources to Helm and Kustomize components. The leaf refers to #Namespaces defined at the root. At the root #Namespaces enforces a constraint: each Namespace must conform to the k8s.io/api/core/v1 specification.

  • At the leaf Holos tailors the component to your platform, mixing in resources and customizing the rendered output.
  • At the root Holos integrates a component with the rest of your platform.

You'll see this pattern again and again as you build your platform.

Render the platform to render the component for the workload clusters.

holos render platform ./platform

Add and commit the configuration and rendered manifests.

git add .
git commit -m "add namespaces component"

#Namespaces is currently empty, so the rendered output of namespaces.gen.yaml is also empty.

tip

Namespaces will be automatically managed as we add more components to the platform over time.

Cert Manager

We'll need a valid certificate to browse to httpbin. We'll manage cert-manager in our cluster to issue valid tls certificates.

Run the following command to generate the cert-manager component.

holos generate component cert-manager

This command generates a configuration file at the leaf and the root. At the leaf two helm values configure the behavior of the upstream cert-manager chart. At the root cert-manager is managed on all clusters in the platform.

  1. The leaf references the version and namespace fields defined in #CertManager at the root.
  2. The leaf defines two Helm values to manage. Holos makes it easier and safer to focus on how software is integrated into our platform.
  3. The root registers cert-manager for the namespaces component to manage consistently across all clusters in the platform.
  4. The root manages the component on all clusters in the platform.

components/cert-manager/cert-manager.cue

package holos

// Produce a helm chart build plan.
(#Helm & Chart).Output

let Chart = {
Name: "cert-manager"
Version: #CertManager.Version
Namespace: #CertManager.Namespace

Repo: name: "jetstack"
Repo: url: "https://charts.jetstack.io"

Values: installCRDs: true
Values: startupapicheck: enabled: false
}

Render the platform to render manifests into the deploy directory.

holos render platform ./platform

Add and commit the configuration and rendered manifests.

git add .
git commit -m "integrate cert-manager into the platform"
tip

We often need to understand how a change affects the platform as a whole. Holos offers the ability to use your preferred tooling to understand platform wide changes.

For example, git summarizes all of the components and clusters affected by adding cert-manager. The output shows both the namespaces and cert-manager components have changed on the workload cluster.

git show --stat deploy

As an optional step, apply the changes.

kubectl apply --server-side=true -f deploy/clusters/workload/components/namespaces
kubectl apply --server-side=true -f deploy/clusters/workload/components/cert-manager

Check the pods become ready

kubectl get pods -n cert-manager

Gateway API

The Gateway API is an official Kubernetes project focused on L4 and L7 routing . You'll use the custom resources defined by the Gateway API to expose the httpbin service outside of the cluster. The Kubernetes Gateway API does not come installed by default on most Kubernetes clusters, so we need to manage the custom resource definitions (CRDs).

Run the following command to generate a Component to manage the Gateway API.

holos generate component gateway-api

The command generates two main configuration files, one at the leaf, and another at the root of the tree. At the leaf, the config produces a Kustomize build plan for Holos to render. At the root, the config adds the Component to all Clusters in the Platform.

Notice the kustomization.yaml file at the leaf. This is an unmodified upstream copy of the standard way to install the Gateway API.

components/gateway-api/gateway-api.cue

package holos

// Produce a kubectl kustomize build plan.
(#Kustomize & {Name: "gateway-api"}).Output
important

We've covered three kinds of components so far: Kubernetes, Helm, and Kustomize.

Holos offers a consistent way to manage these different kinds of packaging safely and easily.

Render the Platform to render the Component for the workload clusters.

holos render platform ./platform
tip

This example is equivalent to running kubectl kustomize ./components/gateway-api and saving the output to a file. Holos simplifies this task and makes it consistent with Helm and other tools.

Add and commit the configuration and rendered manifests.

git add .
git commit -m "add gateway-api component"

As an optional step, apply the rendered component to your cluster.

kubectl apply --server-side=true -f deploy/clusters/workload/components/gateway-api

Istio Service Mesh

We'll manage Istio to implement the Gateway API so we can expose the httpbin service outside of the cluster.

Run the following command to generate the istio components.

holos generate component istio
important

Mix in the istio-k3d component if you're applying the rendered manifests to k3d as described in our Local Cluster guide.

Skip this step if you aren't using k3d. Istio needs to be configured to refer to the nonstandard cni configuration paths k3d uses.

holos generate component istio-k3d

Consistent with the other components we've seen, the istio components define configuration at the root and leafs of the tree. Unlike previous components we've generated, this command generated multiple components to manage Istio.

tree components/istio

These components share the configuration defined at the root in istio.gen.cue.

Let's review how Holos makes it safer and easier to share Helm values defined at the root with the istiod and cni components defined at the leaf.

  1. istiod and cni use version "1.23.1" and namespace "istio-system" defined at the root.
  2. The Helm value to configure ambient (sidecar-less) mode is defined once at the root.
  3. The root adds a constraint to fail validation if the istio system namespace is not "istio-system". Future upgrades are safer with this constraint, if the upstream vendor changes the default in the future the component will fail validation.
  4. The root registers the istio-system namespace with the namespaces component.
  5. The root manages the components on all workload clusters in the platform.

Leaf components/istio/istiod/istiod.cue

package holos

// Produce a helm chart build plan.
(#Helm & Chart).Output

let Chart = {
Name: "istiod"
Version: #Istio.Version
Namespace: #Istio.System.Namespace

Chart: chart: name: "istiod"

Repo: name: "istio"
Repo: url: "https://istio-release.storage.googleapis.com/charts"

Values: #Istio.Values
}
tip

Many software projects managed by Holos are organized into a collection of components working together, for example to safely manage custom resource definitions, secrets, and policy separately from the workloads that rely on them.

Render the platform to render the istio components for the workload clusters.

holos render platform ./platform

Add and commit the configuration and rendered manifests.

git add .
git commit -m "add istio"

Optionally apply the rendered component to your cluster.

kubectl apply --server-side=true -f deploy/clusters/workload/components/namespaces
kubectl apply --server-side=true -f deploy/clusters/workload/components/istio-base
kubectl apply --server-side=true -f deploy/clusters/workload/components/istiod
kubectl apply --server-side=true -f deploy/clusters/workload/components/istio-cni
kubectl apply --server-side=true -f deploy/clusters/workload/components/istio-ztunnel

Make sure all pod containers become ready.

kubectl get pods -A

Once all pods are ready, we're ready for the next step.

Certificate Issuer

We need to issue certificates our browser trusts so we can browse httpbin. We'll do this by configuring a ClusterIssuer using the ca private key we created in the Local Cluster guide.

Run the following command to generate the local-ca component.

holos generate component local-ca

At the leaf, the configuration refers to the #CertManager.Namespace value defined previously at the root by the cert-manager component.

package holos

import ci "cert-manager.io/clusterissuer/v1"

// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output

let Objects = {
Name: "local-ca"
Namespace: #CertManager.Namespace

Resources: ClusterIssuer: LocalCA: ci.#ClusterIssuer & {
metadata: name: "local-ca"
metadata: namespace: #CertManager.Namespace

// The secret name must align with the local cluster guide at
// https://holos.run/docs/guides/local-cluster/
spec: ca: secretName: "local-ca"
}
}

Render the platform to render manifests into the deploy directory.

holos render platform ./platform

Add and commit the configuration and rendered manifests.

git add .
git commit -m "integrate local-ca into the platform"

As an optional step, apply the configuration to the cluster.

kubectl apply --server-side=true -f deploy/clusters/workload/components/local-ca

Verify the local-ca ClusterIssuer is ready.

kubectl get clusterissuers.cert-manager.io

Now that we have a ClusterIssuer we can issue a certificate to expose services outside the cluster.

Ingress Gateway

With the certificate issuer in place, we have everything necessary to manage a Gateway resource. We'll configure the gateway to use a Certificate issued by the local-ca cluster issuer. The gateway terminates the tls connection from the browsers and uses mtls to secure the connection to the backend httpbin deployment.

Generate the component.

holos generate component istio-gateway
warning

TODO: What's going on here?

components/istio/gateway/gateway.cue

package holos

// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output

let Objects = {
Name: "istio-gateway"
Namespace: #Istio.Gateway.Namespace

Resources: {
// The default gateway with all listeners attached to tls certs.
Gateway: default: {
metadata: namespace: Namespace

let Listeners = {
http: {
name: "http"
protocol: "HTTP"
port: 80
allowedRoutes: namespaces: from: "Same"
}
https: {
name: "https"
protocol: "HTTPS"
port: 443
allowedRoutes: namespaces: from: "Same"
tls: mode: "Terminate"
tls: certificateRefs: [{
kind: "Secret"
name: "gateway-cert"
}]
}
}

spec: listeners: [for x in Listeners {x}]
}

// Manage a simple cert for example.com and *.example.com
Certificate: "gateway-cert": {
metadata: name: "gateway-cert"
metadata: namespace: Namespace
spec: commonName: #Platform.Domain
spec: dnsNames: [spec.commonName, "*.\(spec.commonName)"]
spec: secretName: metadata.name
spec: issuerRef: {
kind: "ClusterIssuer"
name: "local-ca"
}
}

// Manage a service account to prevent ArgoCD from pruning it.
ServiceAccount: "default-istio": {
metadata: namespace: Namespace
metadata: labels: {
"gateway.istio.io/managed": "istio.io-gateway-controller"
"gateway.networking.k8s.io/gateway-name": "default"
"istio.io/gateway-name": "default"
}
}
}
}

Render the platform to render manifests into the deploy directory.

holos render platform ./platform

Add and commit the configuration and rendered manifests.

git add .
git commit -m "integrate istio-gateway into the platform"

As an optional step, apply the configuration to the cluster.

kubectl apply --server-side=true -f deploy/clusters/workload/components/namespaces
kubectl apply --server-side=true -f deploy/clusters/workload/components/istio-gateway

Verify the pod is ready.

kubectl get pods -n istio-ingress

Once the gateway is ready we'll move on to managing the backend httpbin service.

httpbin Workload

Generate the component.

holos generate component httpbin-workload
holos generate component referencegrant
warning

REVIEW THE FILES

Render the platform.

holos render platform ./platform

Add and commit the configuration and rendered manifests.

git add .
git commit -m "integrate httpbin workload into the platform"

As an optional step, apply the rendered manifests.

kubectl apply --server-side=true -f deploy/clusters/workload/components/namespaces
kubectl apply --server-side=true -f deploy/clusters/workload/components/httpbin-workload

Verify the pod is ready.

kubectl get pods -n httpbin

We can move on to the next step once httpbin is ready.

HTTP Routes

httpbin is running but still isn't exposed outside of the cluster, so we can't browse to it. We'll add a HTTPRoute to expose the service outside of the cluster.

Generate the component.

holos generate component httpbin-routes
warning

TODO: What's going on here?

components/httpbin/routes/httpbin-routes.cue

package holos

// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output

let Objects = {
Name: "httpbin-routes"
Namespace: #Istio.Gateway.Namespace

Resources: [_]: [_]: metadata: namespace: Namespace
Resources: HTTPRoute: (#HTTPRouteClone & {Name: "httpbin"}).Output
}

#HTTPRouteClone: {
Name: string
let Host = "\(Name).\(#Platform.Domain)"
Output: "\(Name)": {
metadata: namespace: _
metadata: name: Name
metadata: labels: app: Name
spec: hostnames: [Host]
spec: parentRefs: [{
name: "default"
namespace: metadata.namespace
}]
spec: rules: [
{
matches: [{path: {type: "PathPrefix", value: "/"}}]
backendRefs: [{
name: Name
namespace: #HTTPBin.Namespace
port: #HTTPBin.Port
}]
},
]
}
}

Render the platform.

holos render platform ./platform

Add and commit the configuration and rendered manifests.

git add .
git commit -m "expose httpbin using httproutes"

As an optional step, apply the configuration to the cluster.

kubectl apply --server-side=true -f deploy/clusters/workload/components/httpbin-routes

Verify the route is accepted.

kubectl -n istio-ingress describe httproutes.gateway.networking.k8s.io httpbin

We've successfully exposed httpbin once the route is accepted. Browse to https://httpbin.holos.localhost/dump/request and you should see your request headers echoed back to you.

warning

Wrap up