Skip to content

Commit

Permalink
add initial Gateway provisioner (#4415)
Browse files Browse the repository at this point in the history
Adds an initial implementation of the Gateway
provisioner implementing #4401. Code was 
imported from projectcontour/contour-operator
and modified to support the Gateway CRD
instead of the Contour CRD. The provisioner
currently supports only a single instance per
namespace and no parameters. Subsequent
PRs will add more functionality.

Signed-off-by: Steve Kriss <krisss@vmware.com>
  • Loading branch information
skriss authored Mar 30, 2022
1 parent 2b7e6cc commit d99c015
Show file tree
Hide file tree
Showing 56 changed files with 8,181 additions and 14 deletions.
10 changes: 10 additions & 0 deletions changelogs/unreleased/4415-skriss-major.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## Adds a `contour gateway-provisioner` command and deployment manifest for dynamically provisioning Gateways

Contour now has an optional Gateway provisioner, that watches for `Gateway` custom resources and provisions Contour + Envoy instances for them.
The provisioner is implemented as a new subcommand on the `contour` binary, `contour gateway-provisioner`.
The `examples/gateway-provisioner` directory contains the YAML manifests needed to run the provisioner as a Deployment in-cluster.

By default, the Gateway provisioner will process all `GatewayClasses` that have a controller string of `projectcontour.io/gateway-provisioner`, along with all Gateways for them.

The Gateway provisioner is useful for users who want to dynamically provision Contour + Envoy instances based on the `Gateway` CRD.
It is also necessary in order to have a fully conformant Gateway API implementation.
4 changes: 4 additions & 0 deletions cmd/contour/contour.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,12 @@ func main() {
serve, serveCtx := registerServe(app)
version := app.Command("version", "Build information for Contour.")

gatewayProvisioner, gatewayProvisionerConfig := registerGatewayProvisioner(app)

args := os.Args[1:]
switch kingpin.MustParse(app.Parse(args)) {
case gatewayProvisioner.FullCommand():
runGatewayProvisioner(gatewayProvisionerConfig)
case sdm.FullCommand():
doShutdownManager(shutdownManagerCtx)
case sdmShutdown.FullCommand():
Expand Down
166 changes: 166 additions & 0 deletions cmd/contour/gatewayprovisioner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Copyright Project Contour Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"fmt"
"os"

"github.com/projectcontour/contour/internal/provisioner/controller"
"github.com/projectcontour/contour/internal/provisioner/parse"

kingpin "gopkg.in/alecthomas/kingpin.v2"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/manager"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
)

func registerGatewayProvisioner(app *kingpin.Application) (*kingpin.CmdClause, *gatewayProvisionerConfig) {
cmd := app.Command("gateway-provisioner", "Run contour gateway provisioner.")

config := &gatewayProvisionerConfig{
contourImage: "ghcr.io/projectcontour/contour:main",
envoyImage: "docker.io/envoyproxy/envoy:v1.21.1",
metricsBindAddress: ":8080",
leaderElection: false,
leaderElectionID: "0d879e31.projectcontour.io",
gatewayControllerName: "projectcontour.io/gateway-provisioner",
}

cmd.Flag("contour-image", "The container image used for the managed Contour.").
Default(config.contourImage).
StringVar(&config.contourImage)

cmd.Flag("envoy-image", "The container image used for the managed Envoy.").
Default(config.envoyImage).
StringVar(&config.envoyImage)

cmd.Flag("metrics-addr", "The address the metric endpoint binds to. It can be set to 0 to disable serving metrics.").
Default(config.metricsBindAddress).
StringVar(&config.metricsBindAddress)

cmd.Flag("enable-leader-election", "Enable leader election for the gateway provisioner.").
BoolVar(&config.leaderElection)

cmd.Flag("gateway-controller-name", "The controller string to process GatewayClasses and Gateways for.").
Default(config.gatewayControllerName).
StringVar(&config.gatewayControllerName)

return cmd, config
}

type gatewayProvisionerConfig struct {
// contourImage is the container image for the Contour container(s) managed
// by the gateway provisioner.
contourImage string

// envoyImage is the container image for the Envoy container(s) managed
// by the gateway provisioner.
envoyImage string

// metricsBindAddress is the TCP address that the gateway provisioner should bind to for
// serving prometheus metrics. It can be set to "0" to disable the metrics serving.
metricsBindAddress string

// leaderElection determines whether or not to use leader election when starting
// the gateway provisioner.
leaderElection bool

// leaderElectionID determines the name of the configmap that leader election will
// use for holding the leader lock.
leaderElectionID string

// gatewayControllerName defines the controller string that this gateway provisioner instance
// will process GatewayClasses and Gateways for.
gatewayControllerName string
}

func runGatewayProvisioner(config *gatewayProvisionerConfig) {
setupLog := ctrl.Log.WithName("setup")

for _, image := range []string{config.contourImage, config.envoyImage} {
// Parse will not handle short digests.
if err := parse.Image(image); err != nil {
setupLog.Error(err, "invalid image reference", "value", image)
os.Exit(1)
}
}

setupLog.Info("using contour", "image", config.contourImage)
setupLog.Info("using envoy", "image", config.envoyImage)

mgr, err := createManager(ctrl.GetConfigOrDie(), config)
if err != nil {
setupLog.Error(err, "failed to create contour gateway provisioner")
os.Exit(1)
}

setupLog.Info("starting contour gateway provisioner")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "failed to start contour gateway provisioner")
os.Exit(1)
}
}

// createManager creates a new manager from restConfig and provisionerConfig.
func createManager(restConfig *rest.Config, provisionerConfig *gatewayProvisionerConfig) (manager.Manager, error) {
scheme, err := createScheme()
if err != nil {
return nil, fmt.Errorf("error creating runtime scheme: %w", err)
}

mgr, err := ctrl.NewManager(restConfig, manager.Options{
Scheme: scheme,
LeaderElection: provisionerConfig.leaderElection,
LeaderElectionResourceLock: "leases",
LeaderElectionID: provisionerConfig.leaderElectionID,
MetricsBindAddress: provisionerConfig.metricsBindAddress,
Logger: ctrl.Log.WithName("contour-gateway-provisioner"),
})
if err != nil {
return nil, fmt.Errorf("failed to create manager: %w", err)
}

// Create and register the controllers with the manager.
if _, err := controller.NewGatewayClassController(mgr, provisionerConfig.gatewayControllerName); err != nil {
return nil, fmt.Errorf("failed to create gatewayclass controller: %w", err)
}
if _, err := controller.NewGatewayController(mgr, provisionerConfig.gatewayControllerName, provisionerConfig.contourImage, provisionerConfig.envoyImage); err != nil {
return nil, fmt.Errorf("failed to create gateway controller: %w", err)
}
return mgr, nil
}

func createScheme() (*runtime.Scheme, error) {
// scheme contains all the API types necessary for the gateway provisioner's dynamic
// clients to work. Any new non-core types must be added here.
//
// NOTE: The discovery mechanism used by the client doesn't automatically
// refresh, so only add types here that are guaranteed to exist before the
// gateway provisioner starts.
scheme := runtime.NewScheme()

if err := clientgoscheme.AddToScheme(scheme); err != nil {
return nil, err
}
if err := gatewayv1alpha2.AddToScheme(scheme); err != nil {
return nil, err
}

return scheme, nil
}
2 changes: 1 addition & 1 deletion examples/contour/02-role-contour.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# The following ClusterRole is generated from kubebuilder RBAC tags by
# The following ClusterRole and Role are generated from kubebuilder RBAC tags by
# generate-rbac.sh. Do not edit this file directly but instead edit the source
# files and re-render.

Expand Down
11 changes: 11 additions & 0 deletions examples/gateway-provisioner/00-common.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: projectcontour
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: contour-gateway-provisioner
namespace: projectcontour
Loading

0 comments on commit d99c015

Please sign in to comment.