diff --git a/Makefile b/Makefile index 5cb7be5b1..1ac339626 100644 --- a/Makefile +++ b/Makefile @@ -126,10 +126,12 @@ core-run: generate fmt vet manifests # Install CRDs and Definitions of Vela Core into a cluster, this is for develop convenient. core-install: manifests + kubectl apply -f hack/namespace.yaml kubectl apply -f charts/vela-core/crds/ kubectl apply -f charts/vela-core/templates/defwithtemplate/ kubectl apply -f charts/vela-core/templates/definitions/ - bin/vela system update + kubectl apply -f charts/vela-core/templates/velaConfig.yaml + bin/vela workloads # Uninstall CRDs and Definitions of Vela Core from a cluster, this is for develop convenient. core-uninstall: manifests diff --git a/charts/vela-core/templates/velaConfig.yaml b/charts/vela-core/templates/velaConfig.yaml index 701805678..b35bfd7ed 100644 --- a/charts/vela-core/templates/velaConfig.yaml +++ b/charts/vela-core/templates/velaConfig.yaml @@ -2,7 +2,8 @@ apiVersion: v1 kind: ConfigMap metadata: name: vela-config - namespace: {{ .Release.Namespace }} + # TODO: Currently namespace MUST be vela-system + namespace: vela-system data: servicemonitors.monitoring.coreos.com: | { diff --git a/docs/en/developers/set-rollout.md b/docs/en/developers/set-rollout.md index 8f22760ab..c0d8df5ac 100644 --- a/docs/en/developers/set-rollout.md +++ b/docs/en/developers/set-rollout.md @@ -9,8 +9,8 @@ name: testapp services: express-server: type: webservice - image: oamdev/testapp:v1 - port: 8080 + image: oamdev/testapp:rolling01 + port: 80 rollout: replica: 5 @@ -18,7 +18,16 @@ services: interval: "30s" route: - domain: example.com + domain: "example.com" +``` + +If your cluster don't have ingress, you could set domain to be empty like below: + +```yaml +... + route: +- domain: "example.com" ++ domain: "" ``` > The full specification of `rollout` could be found [here](references/traits/rollout.md) @@ -60,9 +69,22 @@ Visiting this app by: ```bash $ curl -H "Host:example.com" http:/// -Hello World% +Hello World -- Rolling 01 ``` +If you don't have ingress in your cluster and you leave domain to be empty, then you could visit this app by: + +```bash +$ vela port-forward testapp --route +Forwarding from 127.0.0.1:8080 -> 80 +Forwarding from [::1]:8080 -> 80 + +Forward successfully! Opening browser ... +Handling connection for 8080 +``` + +It will automatically open browser for you. + In day 2, assuming we have make some changes on our app and build the new image and name it by `oamdev/testapp:v2`. Let's update the appfile by: @@ -72,9 +94,9 @@ name: testapp services: express-server: type: webservice -- image: oamdev/testapp:v1 -+ image: oamdev/testapp:v2 - port: 8080 +- image: oamdev/testapp:rolling01 ++ image: oamdev/testapp:rolling02 + port: 80 rollout: replica: 5 stepWeight: 20 @@ -89,22 +111,51 @@ Apply this `appfile.yaml` again: $ vela up ``` -You could then try to `curl` your app multiple times and and see how the new instances being promoted following Canary rollout strategy: + +You could run `vela status` several times to see the instance rolling: + +```shell script +$ vela status testapp +About: + + Name: testapp + Namespace: myenv + Created at: 2020-11-12 19:02:40.353693 +0800 CST + Updated at: 2020-11-12 19:02:40.353693 +0800 CST + +Services: + + - Name: express-server + Type: webservice + HEALTHY express-server-v2:Ready: 1/1 express-server-v1:Ready: 4/4 + Traits: + - ✅ rollout: interval=30s + replica=5 + stepWeight=20 + - ✅ route: Visiting by using 'vela port-forward testapp --route' + + Last Deployment: + Created at: 2020-11-12 17:20:46 +0800 CST + Updated at: 2020-11-12T19:02:40+08:00 +``` + +You could then try to `curl` your app multiple times and and see how the new instances being promoted following Canary +rollout strategy: (Note: Using `vela port-forward` will not see this as port-forward will proxy network on fixed port, only ingress +has loadbalance.) ```bash $ curl -H "Host:example.com" http:/// -Hello World -- Updated Version Two!% +Hello World -- This is rolling 02 $ curl -H "Host:example.com" http:/// -Hello World% +Hello World -- Rolling 01 $ curl -H "Host:example.com" http:/// -Hello World% +Hello World -- Rolling 01 $ curl -H "Host:example.com" http:/// -Hello World -- Updated Version Two!% +Hello World -- This is rolling 02 $ curl -H "Host:example.com" http:/// -Hello World% +Hello World -- Rolling 01 $ curl -H "Host:example.com" http:/// -Hello World -- Updated Version Two!% +Hello World -- This is rolling 02 ``` For every 30 second, 20% more traffic will be shifted to the new instance from the old instance as we configured in Appfile. - diff --git a/go.mod b/go.mod index 1ec7a37aa..00513aa59 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/briandowns/spinner v1.11.1 github.com/coreos/prometheus-operator v0.41.1 github.com/crossplane/crossplane-runtime v0.10.0 - github.com/crossplane/oam-kubernetes-runtime v0.3.2 + github.com/crossplane/oam-kubernetes-runtime v0.3.3-0.20201112082656-22b7738dcdf3 github.com/fatih/color v1.9.0 github.com/gertd/go-pluralize v0.1.7 github.com/ghodss/yaml v1.0.0 diff --git a/go.sum b/go.sum index af8165631..e67929730 100644 --- a/go.sum +++ b/go.sum @@ -414,8 +414,8 @@ github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/crossplane/crossplane-runtime v0.10.0 h1:H8YvMcrm1uzZYpwU/BpxjRQfceVulxgYJMx4rmX38Hg= github.com/crossplane/crossplane-runtime v0.10.0/go.mod h1:cJl5ZZONisre4v6wTmbrC8Jh3AI+erq/lNaxZzv9tnU= -github.com/crossplane/oam-kubernetes-runtime v0.3.2 h1:iUBsYYn+33X1liRm6sn7oUA2hoXCWW8ik5QtATLZNxk= -github.com/crossplane/oam-kubernetes-runtime v0.3.2/go.mod h1:K4/F1XOPBvmW/PaRSPL3wNA4kCrFGUQC7WkBYcwIGx8= +github.com/crossplane/oam-kubernetes-runtime v0.3.3-0.20201112082656-22b7738dcdf3 h1:0AMLxvQoU14r3qTvGZ8olSwvBq7McL1ZGdiHAQ7L8M8= +github.com/crossplane/oam-kubernetes-runtime v0.3.3-0.20201112082656-22b7738dcdf3/go.mod h1:K4/F1XOPBvmW/PaRSPL3wNA4kCrFGUQC7WkBYcwIGx8= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/daixiang0/gci v0.0.0-20200727065011-66f1df783cb2/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4= diff --git a/hack/namespace.yaml b/hack/namespace.yaml new file mode 100644 index 000000000..9cd7629f6 --- /dev/null +++ b/hack/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: vela-system \ No newline at end of file diff --git a/pkg/application/app.go b/pkg/application/app.go index c858bc310..d48d28dca 100644 --- a/pkg/application/app.go +++ b/pkg/application/app.go @@ -1,6 +1,7 @@ package application import ( + "context" "errors" "fmt" "io/ioutil" @@ -11,6 +12,8 @@ import ( "strings" "time" + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" "github.com/crossplane/oam-kubernetes-runtime/pkg/oam" "github.com/ghodss/yaml" @@ -234,3 +237,11 @@ func getApplicationDir(envName string) (string, error) { _, err := system.CreateIfNotExist(appDir) return appDir, err } + +func GetAppConfig(ctx context.Context, c client.Client, app *Application, env *types.EnvMeta) (*v1alpha2.ApplicationConfiguration, error) { + appConfig := &v1alpha2.ApplicationConfiguration{} + if err := c.Get(ctx, client.ObjectKey{Namespace: env.Namespace, Name: app.Name}, appConfig); err != nil { + return nil, err + } + return appConfig, nil +} diff --git a/pkg/commands/portforward.go b/pkg/commands/portforward.go index d72e6ecc4..7f5ed1298 100644 --- a/pkg/commands/portforward.go +++ b/pkg/commands/portforward.go @@ -9,6 +9,13 @@ import ( "strconv" "strings" + corev1 "k8s.io/api/core/v1" + types2 "k8s.io/apimachinery/pkg/types" + + "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" + + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/crossplane/oam-kubernetes-runtime/pkg/oam" "github.com/oam-dev/kubevela/api/types" "github.com/spf13/cobra" @@ -39,6 +46,8 @@ type VelaPortForwardOptions struct { f k8scmdutil.Factory kcPortForwardOptions *cmdpf.PortForwardOptions ClientSet kubernetes.Interface + Client client.Client + routeTrait bool } func NewPortForwardCommand(c types.Args, ioStreams velacmdutil.IOStreams) *cobra.Command { @@ -49,6 +58,7 @@ func NewPortForwardCommand(c types.Args, ioStreams velacmdutil.IOStreams) *cobra PortForwarder: &defaultPortForwarder{ioStreams}, }, } + cmd := &cobra.Command{ Use: "port-forward APP_NAME [options] [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N]", Short: "Forward one or more local ports to a Pod of a service in an application", @@ -58,6 +68,11 @@ func NewPortForwardCommand(c types.Args, ioStreams velacmdutil.IOStreams) *cobra ioStreams.Error("Please specify application name.") return nil } + newClient, err := client.New(o.VelaC.Config, client.Options{Scheme: o.VelaC.Schema}) + if err != nil { + return err + } + o.Client = newClient if err := o.Init(context.Background(), cmd, args); err != nil { return err } @@ -77,6 +92,7 @@ func NewPortForwardCommand(c types.Args, ioStreams velacmdutil.IOStreams) *cobra cmd.Flags().Duration(podRunningTimeoutFlag, defaultPodExecTimeout, "The length of time (like 5s, 2m, or 3h, higher than zero) to wait until at least one pod is running", ) + cmd.Flags().BoolVar(&o.routeTrait, "route", false, "forward ports from route trait service") return cmd } @@ -111,11 +127,56 @@ func (o *VelaPortForwardOptions) Init(ctx context.Context, cmd *cobra.Command, a return nil } +func GetRouteServiceName(appconfig *v1alpha2.ApplicationConfiguration, svcName string) string { + for _, comp := range appconfig.Status.Workloads { + if comp.ComponentName != svcName { + continue + } + for _, tr := range comp.Traits { + // TODO check from Capability + if tr.Reference.Kind == "Route" && tr.Reference.APIVersion == "standard.oam.dev/v1alpha1" { + return tr.Reference.Name + } + } + } + return "" +} + func (o *VelaPortForwardOptions) Complete() error { svcName, err := util.AskToChooseOneService(o.App.GetComponents()) if err != nil { return err } + if o.routeTrait { + appconfig, err := application.GetAppConfig(o.Context, o.Client, o.App, o.Env) + if err != nil { + return err + } + routeSvc := GetRouteServiceName(appconfig, svcName) + if routeSvc == "" { + return fmt.Errorf("no route trait found in %s %s", o.App.Name, svcName) + } + var svc = corev1.Service{} + err = o.Client.Get(o.Context, types2.NamespacedName{Name: routeSvc, Namespace: o.Env.Namespace}, &svc) + if err != nil { + return err + } + if len(svc.Spec.Ports) <= 0 { + return fmt.Errorf("no port found in service %s", routeSvc) + } + val := strconv.Itoa(int(svc.Spec.Ports[0].Port)) + if val == "80" { + val = "8080:80" + } else if val == "443" { + val = "8443:443" + } + o.Args = append(o.Args, val) + args := make([]string, len(o.Args)) + copy(args, o.Args) + args[0] = "svc/" + routeSvc + return o.kcPortForwardOptions.Complete(o.f, o.Cmd, args) + } + podName, err := o.getPodName(svcName) if err != nil { return err diff --git a/pkg/commands/portforward_test.go b/pkg/commands/portforward_test.go index 0df25e5dd..8dca8b6ca 100644 --- a/pkg/commands/portforward_test.go +++ b/pkg/commands/portforward_test.go @@ -44,6 +44,7 @@ func TestPortForwardCommand(t *testing.T) { kcPortForwardOptions: &portforward.PortForwardOptions{}, f: tf, ClientSet: fakeClientSet, + VelaC: fakeC, } err := o.Init(context.Background(), cmd, []string{"fakeApp", "8081:8080"}) assert.NoError(t, err) diff --git a/pkg/commands/status.go b/pkg/commands/status.go index f0c777617..587111548 100644 --- a/pkg/commands/status.go +++ b/pkg/commands/status.go @@ -403,8 +403,8 @@ func getApp(ctx context.Context, c client.Client, compName, appName string, env return nil, nil, err } - appConfig := &v1alpha2.ApplicationConfiguration{} - if err = c.Get(ctx, client.ObjectKey{Namespace: env.Namespace, Name: app.Name}, appConfig); err != nil { + appConfig, err := application.GetAppConfig(ctx, c, app, env) + if err != nil { return nil, nil, err } return app, appConfig, nil diff --git a/pkg/controller/dependency/install.go b/pkg/controller/dependency/install.go index 2a7fe9223..002d94f31 100644 --- a/pkg/controller/dependency/install.go +++ b/pkg/controller/dependency/install.go @@ -22,6 +22,8 @@ import ( "fmt" "os" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" @@ -55,12 +57,12 @@ func init() { // Note: reconsider delegating the work to helm operator. func Install(kubecli client.Client) { velaConfig, err := fetchVelaConfig(kubecli) - if err != nil { - log.Error(err, "fetchVelaConfig failed") + if apierrors.IsNotFound(err) { + log.Info("no ConfigMap('vela-config') found in vela-system namespace, will not install any dependency") return } - if velaConfig == nil { - log.Info("no vela config") + if err != nil { + log.Error(err, "fetch ConfigMap('vela-config') in vela-system namespace failed") return } for key, chart := range velaConfig.Data { @@ -96,7 +98,7 @@ func fetchVelaConfig(kubecli client.Client) (*v1.ConfigMap, error) { velaConfigNN := k8stypes.NamespacedName{Name: VelaConfigName, Namespace: types.DefaultOAMNS} velaConfig := &v1.ConfigMap{} if err := kubecli.Get(context.TODO(), velaConfigNN, velaConfig); err != nil { - return nil, client.IgnoreNotFound(err) + return nil, err } return velaConfig, nil } diff --git a/pkg/controller/v1alpha1/routes/ingress/nginx_ingress.go b/pkg/controller/v1alpha1/routes/ingress/nginx_ingress.go index 8d2e7007e..9b0effd81 100644 --- a/pkg/controller/v1alpha1/routes/ingress/nginx_ingress.go +++ b/pkg/controller/v1alpha1/routes/ingress/nginx_ingress.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" "strconv" + "strings" standardv1alpha1 "github.com/oam-dev/kubevela/api/v1alpha1" @@ -100,6 +101,10 @@ func (n *Nginx) CheckStatus(routeTrait *standardv1alpha1.Route) (string, []runti func (*Nginx) Construct(routeTrait *standardv1alpha1.Route) []*v1beta1.Ingress { + // Don't create ingress if no host set, this is used for local K8s cluster demo and the route trait will create K8s service only. + if routeTrait.Spec.Host == "" || strings.Contains(routeTrait.Spec.Host, "localhost") || strings.Contains(routeTrait.Spec.Host, "127.0.0.1") { + return nil + } var ingresses []*v1beta1.Ingress for idx, rule := range routeTrait.Spec.Rules { name := rule.Name diff --git a/pkg/controller/v1alpha1/routes/route_controller.go b/pkg/controller/v1alpha1/routes/route_controller.go index b65b48046..798b8e509 100644 --- a/pkg/controller/v1alpha1/routes/route_controller.go +++ b/pkg/controller/v1alpha1/routes/route_controller.go @@ -125,7 +125,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { oamutil.PatchCondition(ctx, r, &routeTrait, cpv1alpha1.ReconcileError(errors.Wrap(err, errApplyNginxIngress))) } - r.record.Event(eventObj, event.Normal("nginx ingress created", + r.record.Event(eventObj, event.Normal("nginx ingress patched", fmt.Sprintf("successfully server side patched a route trait `%s`", routeTrait.Name))) } // TODO(wonderflow): GC mechanism for no used ingress, service, issuer diff --git a/pkg/oam/trait_checker.go b/pkg/oam/trait_checker.go index ecff96aad..7f0683df0 100644 --- a/pkg/oam/trait_checker.go +++ b/pkg/oam/trait_checker.go @@ -132,6 +132,9 @@ func (d *RouteChecker) Check(ctx context.Context, reference runtimev1alpha1.Type } message += fmt.Sprintf("\tVisiting URL: %s\tIP: %s\n", url, addr) } + if len(route.Status.Ingresses) == 0 { + message += fmt.Sprintf("Visiting by using 'vela port-forward %s --route'\n", appConfig.Name) + } return StatusDone, message, nil } diff --git a/pkg/plugins/cluster.go b/pkg/plugins/cluster.go index 60831b1b2..01b719a4c 100644 --- a/pkg/plugins/cluster.go +++ b/pkg/plugins/cluster.go @@ -63,7 +63,7 @@ func GetWorkloadsFromCluster(ctx context.Context, namespace string, c types.Args } gvk, err := util.GetGVKFromDefinition(dm, wd.Spec.Reference) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("make sure you have installed CRD(controller) for this capability '%s': %v ", wd.Name, err) } tmp.CrdInfo = &types.CrdInfo{ APIVersion: gvk.GroupVersion().String(), @@ -99,7 +99,7 @@ func GetTraitsFromCluster(ctx context.Context, namespace string, c types.Args, s } gvk, err := util.GetGVKFromDefinition(dm, td.Spec.Reference) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("make sure you have installed CRD(controller) for this capability '%s': %v ", td.Name, err) } tmp.CrdInfo = &types.CrdInfo{ APIVersion: gvk.GroupVersion().String(),