Skip to content
This repository has been archived by the owner on Sep 8, 2021. It is now read-only.

Add automatic DNS creation using HAR #2

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
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
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ __Root Commands__:
- `run`: Creates a new app, has its own set of flags
- `version`: Provides the version name and number

### Config File

The CLI will look for a config file named `cappsconfig` in three directories in order.

1. First it'll look in the local directory where the script is being executed
2. Then it'll look for it in `$HOME/.capps`
3. Lastly it'll look for it in `$HOME`

If you used the automatic installer, there will be a configuration setting which makes the created DNS zone a default zone

### Create an App

```shell
Expand All @@ -90,7 +100,8 @@ __Flags__
> Since this command will create a new set of workloads, all the logged Docker repositories within the current cluster will work

- `-s`, `--server-url`: (__Required__) The URL for the admin server. To get this, run `kubectl get svc cscaler-proxy -n cscaler -o=jsonpath="{.status.loadBalancer.ingress[*].ip}"`
> Without the correct admin url the scaler __will not__ work
- **Note**: If you used the automatic installer, it'll create a `cappsconfig` file in `$HOME/.capps` with the created DNS name. This DNS will be used as default server URL if this flag is not provided
> Without the correct admin url the scaler __will not__ work

- `-p`, `--port`: Port number to be exposed, should be the port where the app listens to incoming connections.

Expand Down
15 changes: 15 additions & 0 deletions charts/cscaler-proxy/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Thank you for installing {{ .Chart.Name }}.

Your release is named {{ .Release.Name }}.

This release created two ingresses that can be accessed through the following URLs:

- Proxy Admin DNS: http://{{ .Values.proxyAdminSvcName }}.{{ .Values.cscalerProxyDNSZoneName }}

You can access the CLI through the "capps" binary that was placed in your PATH:

- $ capps run <app-name> --image <repository>/<image>:<tag> --port <number> --server-url <url>

Use the admin DNS provided above as the server URL.

NOTE: It might take a couple of minutes before the DNS record is propagated
22 changes: 22 additions & 0 deletions charts/cscaler-proxy/templates/proxy-admin-ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ .Values.proxyAdminSvcName }}
namespace: {{ .Release.Namespace }}
labels:
name: {{ .Values.proxyAdminSvcName }}
app: cscaler
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
annotations:
kubernetes.io/ingress.class: addon-http-application-routing
spec:
rules:
- host: {{ .Values.proxyAdminSvcName }}.{{ .Values.cscalerProxyDNSZoneName }}
http:
paths:
- path: /
backend:
serviceName: {{ .Values.proxyAdminSvcName }}
servicePort: {{ .Values.proxyAdminSvcPort }}
4 changes: 2 additions & 2 deletions charts/cscaler-proxy/templates/proxy-admin-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ metadata:
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
spec:
type: LoadBalancer
type: ClusterIP
ports:
- name: web
protocol: TCP
Expand All @@ -19,4 +19,4 @@ spec:
selector:
name: cscaler-proxy
app: {{ template "fullname" . }}
release: "{{ .Release.Name }}"
release: "{{ .Release.Name }}"
4 changes: 2 additions & 2 deletions charts/cscaler-proxy/templates/proxy-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ spec:
serviceAccountName: cscaler-proxy
containers:
- name: {{ template "fullname" . }}
image: "arschles/cscaler:latest"
image: "khaosdoctor/cscaler-proxy:latest"
imagePullPolicy: Always
ports:
- containerPort: {{ .Values.proxyHTTPSvcPort }}
Expand All @@ -39,4 +39,4 @@ spec:
- name: PROXY_PORT
value: {{ .Values.proxyHTTPSvcPort | quote }}
- name: GRPC_PORT
value: {{ .Values.proxyGRPCSvcPort | quote }}
value: {{ .Values.proxyGRPCSvcPort | quote }}
4 changes: 2 additions & 2 deletions charts/cscaler-proxy/templates/proxy-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ metadata:
name: cscaler-proxy
namespace: {{ .Release.Namespace }}
rules:
- apiGroups: ["", "apps", "scaledobjects.keda.k8s.io", "keda.k8s.io"] # "" indicates the core API group
resources: ["deployments", "services", "scaledobjects"]
- apiGroups: ["", "apps", "scaledobjects.keda.k8s.io", "keda.k8s.io", "extensions"] # "" indicates the core API group
resources: ["deployments", "services", "scaledobjects", "ingresses"]
verbs: ["get", "watch", "list", "create", "delete"]
3 changes: 2 additions & 1 deletion charts/cscaler-proxy/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ proxyAdminSvcPort: 8081
proxyGRPCSvcName: cscaler-proxy-internal
proxyGRPCSvcPort: 9090

proxyDeployName: cscaler-proxy
proxyDeployName: cscaler-proxy
cscalerProxyDNSZoneName: localhost
9 changes: 7 additions & 2 deletions cmd/cli/commands/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/hashicorp/go-multierror"
"github.com/parnurzeal/gorequest"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func newDeployCmd() *cobra.Command {
Expand All @@ -29,7 +30,11 @@ func newDeployCmd() *cobra.Command {
serverProtocol = "http"
}

deployURL := fmt.Sprintf("%s://%s/app", serverProtocol, serverURL)
deployURL := fmt.Sprintf("%s://%s/app", serverProtocol, viper.GetViper().GetString("server_url"))
if serverURL != "" {
fmt.Printf("Overriding config file server URL for \"%s\"\n", serverURL)
deployURL = fmt.Sprintf("%s://%s/app", serverProtocol, serverURL)
}
fmt.Println("Using server ", deployURL)

resp, body, errs := cl.Post(deployURL).Send(map[string]string{
Expand Down Expand Up @@ -59,7 +64,7 @@ func newDeployCmd() *cobra.Command {
&serverURL,
"server-url",
"s",
"admin.wtfcncf.dev",
"",
"The URL to the admin server (without the 'http' prefix)",
)

Expand Down
9 changes: 6 additions & 3 deletions cmd/cli/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var (
userLicense string

rootCmd = &cobra.Command{
Use: "cscaler",
Use: "capps",
Short: "Auto-scaling containers",
Long: `This project implements a prototype of auto-scaling containers on either Kubernetes or ACI`,
}
Expand Down Expand Up @@ -48,9 +48,12 @@ func initConfig() {
er(err)
}

// Search config in home directory with name ".cobra" (without extension).
// Search for config files named "cappsconfig" first in local dir, then $HOME/.capps then $HOME
viper.AddConfigPath(".")
viper.AddConfigPath(home + "/.capps/")
viper.AddConfigPath(home)
viper.SetConfigName(".cobra")
viper.SetConfigType("yaml")
viper.SetConfigName("cappsconfig")
}

viper.AutomaticEnv()
Expand Down
18 changes: 13 additions & 5 deletions cmd/cli/commands/undeploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/hashicorp/go-multierror"
"github.com/parnurzeal/gorequest"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func newUndeployCmd() *cobra.Command {
Expand All @@ -18,20 +19,27 @@ func newUndeployCmd() *cobra.Command {
Long: `Remove an app completely. This will delete all resources associated with the app, including the running container and scaling configuration`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
appName := args[0]
if len(args) != 1 {
log.Fatalf("You need to pass the deployment name")
}
cl := gorequest.New()

serverProtocol := "https"
if acceptsHTTP == true {
serverProtocol = "http"
}

deployURL := fmt.Sprintf("%s://%s/app", serverProtocol, serverURL)
deployURL := fmt.Sprintf("%s://%s/app", serverProtocol, viper.GetViper().GetString("server_url"))
if serverURL != "" {
fmt.Printf("Overriding config file server URL for \"%s\"\n", serverURL)
deployURL = fmt.Sprintf("%s://%s/app", serverProtocol, serverURL)
}
fmt.Println("Using server ", deployURL)

resp, body, errs := cl.Delete(deployURL).Send(nil).End()
cl := gorequest.New().Delete(deployURL)
cl.QueryData.Add("name", appName)

resp, body, errs := cl.Send(nil).End()
if len(errs) > 0 {
var result error
log.Printf("Error deleting: %v", errs)
Expand All @@ -41,15 +49,15 @@ func newUndeployCmd() *cobra.Command {
log.Fatalf("Undeploy failed: %s", body)
}

log.Printf("App %s deleted!", args[0])
log.Printf("App %s deleted!", appName)
return nil
},
}
undeployCmd.Flags().StringVarP(
&serverURL,
"server-url",
"s",
"admin.wtfcncf.dev",
"",
"The URL to the admin server (without the 'http' prefix)",
)

Expand Down
42 changes: 33 additions & 9 deletions cmd/proxy/admin_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"strconv"
"strings"

"github.com/arschles/containerscaler/pkg/k8s"
echo "github.com/labstack/echo/v4"
Expand All @@ -11,6 +12,9 @@ import (
"k8s.io/client-go/kubernetes"
)

// NAMESPACE NAME
const NAMESPACE string = "cscaler"

func newAdminDeleteAppHandler(
k8sCl *kubernetes.Clientset,
dynCl dynamic.Interface,
Expand All @@ -19,16 +23,20 @@ func newAdminDeleteAppHandler(
logger := c.Logger()
ctx := c.Request().Context()
deployName := c.QueryParam("name")
scaledObjectCl := k8s.NewScaledObjectClient(dynCl).Namespace("cscaler")
scaledObjectCl := k8s.NewScaledObjectClient(dynCl).Namespace(NAMESPACE)
if deployName == "" {
logger.Errorf("'name' query param not found")
return c.String(400, "'name' query param required")
}
if err := k8s.DeleteService(ctx, deployName, k8sCl.CoreV1().Services("cscaler")); err != nil {
if err := k8s.DeleteService(ctx, deployName, k8sCl.CoreV1().Services(NAMESPACE)); err != nil {
logger.Errorf("Deleting service %s (%s)", err)
return c.String(500, "deleting service")
}
if err := k8s.DeleteDeployment(ctx, deployName, k8sCl.AppsV1().Deployments("cscaler")); err != nil {
if err := k8s.DeleteIngress(ctx, deployName, k8sCl.ExtensionsV1beta1().Ingresses(NAMESPACE)); err != nil {
logger.Errorf("Deleting ingress %s (%s)", err)
return c.String(500, "deleting ingress")
}
if err := k8s.DeleteDeployment(ctx, deployName, k8sCl.AppsV1().Deployments(NAMESPACE)); err != nil {
logger.Errorf("Deleting deployment %s (%s)", deployName, err)
return c.String(500, "deleting deployment")
}
Expand Down Expand Up @@ -69,23 +77,39 @@ func newAdminCreateAppHandler(
return c.String(400, "invalid port")
}

appsCl := k8sCl.AppsV1().Deployments("cscaler")
deployment := k8s.NewDeployment(ctx, "cscaler", req.Name, req.ContainerImage, int32(portInt))
appsCl := k8sCl.AppsV1().Deployments(NAMESPACE)
deployment := k8s.NewDeployment(ctx, NAMESPACE, req.Name, req.ContainerImage, int32(portInt))
// TODO: watch the deployment until it reaches ready state
if _, err := appsCl.Create(ctx, deployment, metav1.CreateOptions{}); err != nil {
c.Logger().Errorf("Creating deployment (%s)", err)
return c.String(500, "creating deployment")
}

coreCl := k8sCl.CoreV1().Services("cscaler")
service := k8s.NewService("cscaler", req.Name, int32(portInt))
coreCl := k8sCl.CoreV1().Services(NAMESPACE)
service := k8s.NewService(NAMESPACE, req.Name, int32(portInt))
if _, err := coreCl.Create(ctx, service, metav1.CreateOptions{}); err != nil {
c.Logger().Errorf("Creating service (%s)", err)
return c.String(500, "creating service")
}

coreIng := k8sCl.ExtensionsV1beta1().Ingresses(NAMESPACE)
dnsZoneName := ""
cscalerAdminIngress, err := coreIng.Get(ctx, "cscaler-admin", metav1.GetOptions{});
if err != nil {
c.Logger().Errorf("Getting proxy DNS zone name (%s)", err)
return c.String(500, "getting proxy DNS Zone Name")
}
dnsZoneName = strings.Join(strings.Split(cscalerAdminIngress.Spec.Rules[0].Host, ".")[1:], ".") // Get HAR hostname (DNS Zone Name)

ingress := k8s.NewIngress(NAMESPACE, req.Name, dnsZoneName)
if _, err := coreIng.Create(ctx, ingress, metav1.CreateOptions{}); err != nil {
c.Logger().Errorf("Creating ingress (%s)", err)
return c.String(500, "creating ingress")
}

scaledObjectCl := k8s.NewScaledObjectClient(dynCl)
_, err = scaledObjectCl.Namespace("cscaler").Create(ctx, k8s.NewScaledObject(
"cscaler",
_, err = scaledObjectCl.Namespace(NAMESPACE).Create(ctx, k8s.NewScaledObject(
NAMESPACE,
req.Name,
req.Name,
scalerAddress,
Expand Down
9 changes: 6 additions & 3 deletions cmd/proxy/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@ import (
func getSvcName(host string) (string, error) {
hostSpl := strings.Split(host, ".")
log.Printf("split for host %s: %v", host, hostSpl)
if len(hostSpl) != 3 {
possibleHost := hostSpl[0]

if possibleHost == "" {
return "", fmt.Errorf("Host string %s malformed", host)
}
return hostSpl[0], nil

return possibleHost, nil
}

// TODO: use proxy handler: https://echo.labstack.com/middleware/proxy ??
func newForwardingHandler() echo.HandlerFunc {
return func(c echo.Context) error {

log.Printf("Host: %s sent a request", c.Request().Host)
svcName, err := getSvcName(c.Request().Host)
if err != nil {
log.Printf("Couldn't find service name (%s)", err)
Expand Down
51 changes: 51 additions & 0 deletions pkg/k8s/ingress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package k8s

import (
context "context"
"strings"

extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
k8sextensionsv1beta1 "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
)

func DeleteIngress(ctx context.Context, name string, cl k8sextensionsv1beta1.IngressInterface) error {
return cl.Delete(ctx, name, metav1.DeleteOptions{})
}

func NewIngress(namespace, name string, dnsZoneName string) *extensionsv1beta1.Ingress {
return &extensionsv1beta1.Ingress{
TypeMeta: metav1.TypeMeta{
Kind: "Ingress",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: "cscaler",
Labels: labels(name),
Annotations: map[string]string{
"kubernetes.io/ingress.class": "addon-http-application-routing",
},
},
Spec: extensionsv1beta1.IngressSpec{
Rules: []extensionsv1beta1.IngressRule{
{
Host: strings.Join([]string{name, dnsZoneName}, "."),
IngressRuleValue: extensionsv1beta1.IngressRuleValue{
HTTP: &extensionsv1beta1.HTTPIngressRuleValue{
Paths: []extensionsv1beta1.HTTPIngressPath{
{
Path: "/",
Backend: extensionsv1beta1.IngressBackend{
ServiceName: "cscaler-proxy",
ServicePort: intstr.FromString("web"),
},
},
},
},
},
},
},
},
}
}
Loading