Skip to content

Commit

Permalink
Adds Envoy Deployment Support
Browse files Browse the repository at this point in the history
Signed-off-by: danehans <[email protected]>
  • Loading branch information
danehans committed Aug 11, 2022
1 parent 6c4bf9b commit 017dee1
Show file tree
Hide file tree
Showing 4 changed files with 428 additions and 9 deletions.
232 changes: 232 additions & 0 deletions internal/infrastructure/kubernetes/deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
package kubernetes

import (
"context"
"fmt"
"path/filepath"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"

"github.com/envoyproxy/gateway/internal/ir"
)

const (
// envoyContainerName is the name of the Envoy container.
envoyContainerName = "envoy"
// envoyNsEnvVar is the name of the contour namespace environment variable.
envoyNsEnvVar = "ENVOY_GATEWAY_NAMESPACE"
// envoyPodEnvVar is the name of the Envoy pod name environment variable.
envoyPodEnvVar = "ENVOY_POD_NAME"
// envoyCfgVolName is the name of the Envoy configuration volume.
envoyCfgVolName = "envoy-config"
// envoyCfgVolMntDir is the directory name of the Envoy configuration volume.
envoyCfgVolMntDir = "config"
// envoyCfgFileName is the name of the Envoy configuration file.
envoyCfgFileName = "envoy.json"
// envoyHTTPPort is the container port number of Envoy's HTTP endpoint.
envoyHTTPPort = int32(8080)
// envoyHTTPSPort is the container port number of Envoy's HTTPS endpoint.
envoyHTTPSPort = int32(8443)
)

// createDeploymentIfNeeded creates a Deployment based on the provided infra, if
// it doesn't exist in the kube api server.
func (i *Infra) createDeploymentIfNeeded(ctx context.Context, infra *ir.Infra) error {
current, err := i.getDeployment(ctx, infra)
if err != nil {
if kerrors.IsNotFound(err) {
deploy, err := i.createDeployment(ctx, infra)
if err != nil {
return err
}
if err := i.addResource(deploy); err != nil {
return err
}
return nil
}
return err
}

if err := i.addResource(current); err != nil {
return err
}

return nil
}

// getDeployment gets the Deployment for the provided infra from the kube api.
func (i *Infra) getDeployment(ctx context.Context, infra *ir.Infra) (*appsv1.Deployment, error) {
ns := i.Namespace
name := infra.Proxy.Name
key := types.NamespacedName{
Namespace: ns,
Name: infra.GetProxyInfra().ObjectName(),
}
deploy := new(appsv1.Deployment)
if err := i.Client.Get(ctx, key, deploy); err != nil {
return nil, fmt.Errorf("failed to get deployment %s/%s: %w", ns, name, err)
}

return deploy, nil
}

// expectedDeployment returns the expected Deployment based on the provided infra.
func (i *Infra) expectedDeployment(infra *ir.Infra) *appsv1.Deployment {
containers := expectedContainers(infra)

deployment := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: i.Namespace,
Name: infra.GetProxyInfra().ObjectName(),
Labels: envoyLabels(),
},
Spec: appsv1.DeploymentSpec{
Replicas: pointer.Int32(1),
Selector: EnvoyPodSelector(),
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: EnvoyPodSelector().MatchLabels,
},
Spec: corev1.PodSpec{
Containers: containers,
Volumes: []corev1.Volume{
{
Name: envoyCfgVolName,
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
},
},
ServiceAccountName: infra.Proxy.ObjectName(),
AutomountServiceAccountToken: pointer.BoolPtr(false),
TerminationGracePeriodSeconds: pointer.Int64Ptr(int64(300)),
SecurityContext: newUnprivilegedPodSecurity(),
DNSPolicy: corev1.DNSClusterFirst,
RestartPolicy: corev1.RestartPolicyAlways,
SchedulerName: "default-scheduler",
},
},
},
}

return deployment
}

func expectedContainers(infra *ir.Infra) []corev1.Container {
ports := []corev1.ContainerPort{
{
Name: "http",
ContainerPort: envoyHTTPPort,
Protocol: corev1.ProtocolTCP,
},
{
Name: "https",
ContainerPort: envoyHTTPSPort,
Protocol: corev1.ProtocolTCP,
},
}

containers := []corev1.Container{
{
Name: envoyContainerName,
Image: infra.Proxy.Image,
ImagePullPolicy: corev1.PullIfNotPresent,
Command: []string{
"envoy",
},
Args: []string{
"-c",
filepath.Join("/", envoyCfgVolMntDir, envoyCfgFileName),
fmt.Sprintf("--service-cluster $(%s)", envoyNsEnvVar),
fmt.Sprintf("--service-node $(%s)", envoyPodEnvVar),
"--log-level info",
},
Env: []corev1.EnvVar{
{
Name: envoyNsEnvVar,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.namespace",
},
},
},
{
Name: envoyPodEnvVar,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.name",
},
},
},
},
Ports: ports,
VolumeMounts: []corev1.VolumeMount{
{
Name: envoyCfgVolName,
MountPath: filepath.Join("/", envoyCfgVolMntDir),
ReadOnly: true,
},
},
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
TerminationMessagePath: "/dev/termination-log",
},
}

return containers
}

// createDeployment creates a Deployment in the kube api server based on the provided
// infra, if it doesn't exist.
func (i *Infra) createDeployment(ctx context.Context, infra *ir.Infra) (*appsv1.Deployment, error) {
expected := i.expectedDeployment(infra)
err := i.Client.Create(ctx, expected)
if err != nil {
if kerrors.IsAlreadyExists(err) {
return expected, nil
}
return nil, fmt.Errorf("failed to create deployment %s/%s: %w",
expected.Namespace, expected.Name, err)
}

return expected, nil
}

// EnvoyPodSelector returns a label selector using "control-plane: envoy-gateway" as the
// key/value pair.
//
// TODO: Update k/v pair to use gatewayclass controller name to distinguish between
// multiple Envoy Gateways.
func EnvoyPodSelector() *metav1.LabelSelector {
return &metav1.LabelSelector{
MatchLabels: envoyLabels(),
}
}

// newUnprivilegedPodSecurity makes a non-root PodSecurityContext object
// using 65534 as the user and group ID.
func newUnprivilegedPodSecurity() *corev1.PodSecurityContext {
user := int64(65534)
group := int64(65534)
nonRoot := true
return &corev1.PodSecurityContext{
RunAsUser: &user,
RunAsGroup: &group,
RunAsNonRoot: &nonRoot,
}
}

// envoyLabels returns the labels used for Envoy.
func envoyLabels() map[string]string {
return map[string]string{"control-plane": "envoy-gateway"}
}
Loading

0 comments on commit 017dee1

Please sign in to comment.