Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate new deployment for each app version #63

Merged
merged 1 commit into from
Oct 6, 2023
Merged
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
73 changes: 26 additions & 47 deletions internal/impl/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ type replicaSet struct {

// ListenerOptions stores configuration options for a listener.
type ListenerOptions struct {
// If specified, the listener service will have the name set to this value.
// Otherwise, we will generate a unique name for each app version.
ServiceName string `toml:"service_name"`

// Is the listener public, i.e., should it receive ingress traffic
// from the public internet. If false, the listener is configured only
// for cluster-internal access.
Expand Down Expand Up @@ -168,11 +172,6 @@ type KubeConfig struct {
Observability map[string]string
}

// globalName returns an unique name that persists across app versions.
func (r *replicaSet) globalName() string {
return name{r.dep.App.Name, r.name}.DNSLabel()
}

// deploymentName returns a name that is version specific.
func (r *replicaSet) deploymentName() string {
return name{r.dep.App.Name, r.name, r.dep.Id[:8]}.DNSLabel()
Expand All @@ -184,23 +183,13 @@ func (r *replicaSet) deploymentName() string {
// collocated with main, and a component bar that is not collocated with main
// calls foo.
func (r *replicaSet) buildDeployment(cfg *KubeConfig) (*appsv1.Deployment, error) {
matchLabels := map[string]string{}
name := r.deploymentName()
matchLabels := map[string]string{"depName": name}
podLabels := map[string]string{
"appName": r.dep.App.Name,
"depName": r.deploymentName(),
"depName": name,
"metrics": r.dep.App.Name, // Needed by Prometheus to scrape the metrics.
}
name := r.deploymentName()
if r.hasListeners() {
name = r.globalName()

// Set the match and the pod labels, so they can be reachable across
// multiple app versions.
matchLabels["globalName"] = r.globalName()
podLabels["globalName"] = r.globalName()
} else {
matchLabels["depName"] = r.deploymentName()
}
dnsPolicy := corev1.DNSClusterFirst
if cfg.UseHostNetwork {
dnsPolicy = corev1.DNSClusterFirstWithHostNet
Expand All @@ -218,6 +207,7 @@ func (r *replicaSet) buildDeployment(cfg *KubeConfig) (*appsv1.Deployment, error
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: r.namespace,
Labels: map[string]string{"version": r.dep.Id[:8]},
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
Expand All @@ -234,13 +224,6 @@ func (r *replicaSet) buildDeployment(cfg *KubeConfig) (*appsv1.Deployment, error
HostNetwork: cfg.UseHostNetwork,
},
},
Strategy: appsv1.DeploymentStrategy{
Type: "RollingUpdate",
RollingUpdate: &appsv1.RollingUpdateDeployment{},
},
// Number of old ReplicaSets to retain to allow rollback.
RevisionHistoryLimit: ptrOf(int32(1)),
MinReadySeconds: int32(5),
},
}, nil
}
Expand All @@ -251,10 +234,13 @@ func (r *replicaSet) buildDeployment(cfg *KubeConfig) (*appsv1.Deployment, error
// it has to be reachable from the outside; for internal listeners, we generate
// a ClusterIP service, reachable only from internal Service Weaver services.
func (r *replicaSet) buildListenerService(lis *ReplicaSetConfig_Listener) (*corev1.Service, error) {
// Unique name that persists across app versions.
// TODO(rgrandl): Specify whether the listener is public in the name.
globalLisName := name{r.dep.App.Name, "lis", lis.Name}.DNSLabel()

// If the service name for the listener is not specified by the user, generate
// a deployment based service name.
lisServiceName := lis.ServiceName
if lisServiceName == "" {
lisServiceName = name{r.dep.App.Name, "lis", lis.Name, r.dep.Id[:8]}.DNSLabel()
}
var serviceType string
if lis.IsPublic {
serviceType = "LoadBalancer"
Expand All @@ -268,16 +254,17 @@ func (r *replicaSet) buildListenerService(lis *ReplicaSetConfig_Listener) (*core
Kind: "Service",
},
ObjectMeta: metav1.ObjectMeta{
Name: globalLisName,
Name: lisServiceName,
Namespace: r.namespace,
Labels: map[string]string{
"lisName": lis.Name,
"version": r.dep.Id[:8],
},
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceType(serviceType),
Selector: map[string]string{
"globalName": r.globalName(),
"depName": r.deploymentName(),
},
Ports: []corev1.ServicePort{
{
Expand All @@ -294,13 +281,7 @@ func (r *replicaSet) buildListenerService(lis *ReplicaSetConfig_Listener) (*core
func (r *replicaSet) buildAutoscaler() (*autoscalingv2.HorizontalPodAutoscaler, error) {
// Per deployment name that is app version specific.
aname := name{r.dep.App.Name, "hpa", r.name, r.dep.Id[:8]}.DNSLabel()

var depName string
if r.hasListeners() {
depName = r.globalName()
} else {
depName = r.deploymentName()
}
depName := r.deploymentName()
return &autoscalingv2.HorizontalPodAutoscaler{
TypeMeta: metav1.TypeMeta{
APIVersion: "autoscaling/v2",
Expand All @@ -309,6 +290,7 @@ func (r *replicaSet) buildAutoscaler() (*autoscalingv2.HorizontalPodAutoscaler,
ObjectMeta: metav1.ObjectMeta{
Name: aname,
Namespace: r.namespace,
Labels: map[string]string{"version": r.dep.Id[:8]},
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
Expand Down Expand Up @@ -401,16 +383,6 @@ func (r *replicaSet) buildContainer() (corev1.Container, error) {
}, nil
}

// hasListeners returns whether a given replica set exports any listeners.
func (r *replicaSet) hasListeners() bool {
for _, listeners := range r.components {
if listeners.Listeners != nil {
return true
}
}
return false
}

// GenerateYAMLs generates Kubernetes YAML configurations for a given
// application version.
//
Expand Down Expand Up @@ -443,6 +415,8 @@ func GenerateYAMLs(image string, dep *protos.Deployment, cfg *KubeConfig) error

// Generate roles and role bindings.
var generated []byte
header := fmt.Sprintf("# To delete this deployment run:\n# `kubectl delete all --selector=version=%s`\n\n", dep.Id[:8])
rgrandl marked this conversation as resolved.
Show resolved Hide resolved
generated = append(generated, []byte(header)...)
content, err := generateRolesAndBindings(cfg.Namespace)
if err != nil {
return fmt.Errorf("unable to generate roles and bindings: %w", err)
Expand Down Expand Up @@ -728,8 +702,13 @@ func readBinary(dep *protos.Deployment, cfg *KubeConfig) ([]*ReplicaSetConfig_Co
port = externalPort
externalPort++
}
var serviceName string
if opts := cfg.Listeners[lis]; opts != nil && opts.ServiceName != "" {
serviceName = opts.ServiceName
}
c.Listeners = append(c.Listeners, &ReplicaSetConfig_Listener{
Name: lis,
ServiceName: serviceName,
ExternalPort: port,
IsPublic: public,
})
Expand Down
38 changes: 24 additions & 14 deletions internal/impl/kube.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions internal/impl/kube.proto
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ message ReplicaSetConfig {
}
message Listener {
string name = 1;
int32 external_port = 2;
bool is_public = 3;
string service_name = 2;
int32 external_port = 3;
bool is_public = 4;
}
repeated Component components = 6;
}
Loading