wasmCloud/wadm

[FEAT] Support sharing components/providers between manifests

brooksmtownsend opened this issue · 9 comments

Just like named configuration can be specified solely by name, without specifying the properties, and sourced from outside of an application, it would be great to be able to share a component or capability provider between different applications to cut down on duplication of compute.

For example, a minimal HTTP component manifest:

apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: rust-hello-world
  annotations:
    version: v0.0.1
    description: "HTTP hello world demo in Rust, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)"
spec:
  components:
    - name: http-component
      type: component
      properties:
        image: file://./build/http_hello_world_s.wasm
      traits:
        # Govern the spread/scheduling of the component
        - type: spreadscaler
          properties:
            replicas: 1

    # Add a capability provider that enables HTTP access
    - name: httpserver
      type: capability
      properties:
        image: ghcr.io/wasmcloud/http-server:0.20.0
      traits:
        # Link the httpserver to the component, and configure the HTTP server
        # to listen on port 8080 for incoming requests
        - type: link
          properties:
            target: http-component
            namespace: wasi
            package: http
            interfaces: [incoming-handler]
            source_config:
              - name: default-http
                properties:
                  address: 127.0.0.1:8080

If you imagine we have two of these basic HTTP component YAMLs, most of the configuration will look the same but we'll end up using two HTTP servers to facilitate communication.

To throw out a strawman, one possible solution to this would be to reference a provider by name and, optionally, manifest:

apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: managed
  annotations:
    version: v0.0.1
    description: "Managed shared provider"
spec:
  components:
    - name: httpserver
      type: capability
      properties:
        image: ghcr.io/wasmcloud/http-server:0.20.0

Then, in our updated application manifest:

apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: rust-hello-world
  annotations:
    version: v0.0.1
    description: "HTTP hello world demo in Rust, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)"
spec:
  components:
    - name: http-component
      type: component
      properties:
        image: file://./build/http_hello_world_s.wasm
      traits:
        # Govern the spread/scheduling of the component
        - type: spreadscaler
          properties:
            replicas: 1

    # Reuse a capability from a different manifest
    - name: httpserver
      type: capability
      properties:
        app: managed
        component: httpserver
      traits:
        # Link the httpserver to the component, and configure the HTTP server
        # to listen on port 8080 for incoming requests
        - type: link
          properties:
            target: http-component
            namespace: wasi
            package: http
            interfaces: [incoming-handler]
            source_config:
              - name: default-http
                properties:
                  address: 127.0.0.1:8080

The benefit of only allowing references to other applications is validation, which would let us look up dependent providers before deploying an application (and to warn when undeploying the managed application)

This could be a real can of worms, but it's definitely worth it.

  • There are some security implications here. It's been awhile since I discussed security and wasmcloud, so I forget where the trust boundaries are. I don't know if you can always assume that components under the same NATS topic can use each other's providers. Using someone else's MySQL provider would make for a very bad day.
  • We may want to introduce a "weakchildof" concept to allow the original creator of the provider to be deleted without removing the shared provider. As of the last time I looked at Kubernetes, I don't think it ever really figured that out, but the concept certainly exists in ECS and other data structures.

Here is what @thomastaylor312 and I brainstormed here:

  • Add metadata annotation wasmcloud.dev/shared=true
  • Marking a manifest as shared means contents can be linked to/from in other manifests in that lattice

To change an example manifest from an HTTP server to a shared HTTP server:

Before

apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: dog-fetcher
  annotations:
    description: 'An app that gets dog pics'
spec:
  components:
    - name: dog-fetcher
      type: component
      properties:
        image: ghcr.io/wasmcloud/components/dog-fetcher-rust:0.1.0
      traits:
        - type: spreadscaler
          properties:
            instances: 5

    - name: httpserver
      type: capability
      properties:
        image: ghcr.io/wasmcloud/http-server:0.21.0
      traits:
        - type: link
          properties:
            target: dog-fetcher
            namespace: wasi
            package: http
            interfaces:
              - incoming-handler
            source_config:
              - name: dog-fetcher-address
                properties:
                  address: 0.0.0.0:8081

After, shared manifest:

apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: network
  annotations:
    description: 'Shared platform network capability providers'
    wasmcloud.dev/shared: 'true'
spec:
  components:
    - name: httpserver
      type: capability
      properties:
        image: ghcr.io/wasmcloud/http-server:0.21.0

App manifest:

apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: dog-fetcher
  annotations:
    description: 'An app that gets dog pics'
spec:
  components:
    - name: dog-fetcher
      type: component
      properties:
        image: ghcr.io/wasmcloud/components/dog-fetcher-rust:0.1.0
      traits:
        - type: spreadscaler
          properties:
            instances: 5

    - name: httpserver
      type: capability
      properties:
        # This references a component `httpserver` in manifest `network`
        # Other manifest must be annotated with `wasmcloud.dev/shared=true`
        #     or this will be rejected at put time.
        manifest:
          name: network
          component: httpserver
      traits:
        - type: link
          properties:
            target: dog-fetcher
            namespace: wasi
            package: http
            interfaces:
              - incoming-handler
            source_config:
              - name: dog-fetcher-address
                properties:
                  address: 0.0.0.0:8081

As a part of putting the manifest we would resolve any manifest: references by looking for the other manifest by name, checking the shared annotation, and then if set to true see if that component exists. If the other manifest exists but is not deployed, we at least need to return a warning that this app cannot be deployed successfully, but this is probably an error.

We would do a similar lookup at deploy time to properly link from/to the component in the other manifest based on its actual component ID in the lattice.

The "weakchildof" concept that @cdmurph32 sounds great IMO, what we should absolutely do for this feature is prevent undeploying resources that other apps depend on (perhaps without a force). We may be able to detail a couple of strategies for the undeploy / delete such that the delete can be non_destructive, destructive, or a weakchild strategy where resources stick around if they are depended on

Supercedes #120

I like the option of shared manifests . Isn't the naming confusing though ?
The provider is being referred to as a component in this shared manifest .

 manifest:
          name: network
          component: httpserver
         

Do you think it would be better to call this provider itself ?
Potentially we could have components and providers deployed as part of the shared manifests.

Isn't the naming confusing though ?
The provider is being referred to as a component in this shared manifest .

It is somewhat, what we're really referring to is the component keyword in the manifest, not webassembly component. We're overloading the term here, but happy to take suggestions

spec:
  components:
    - name: dog-fetcher
      type: component
...
    - name: httpserver
      type: capability

Still thinking through the ramifications of this change, but I think naming is a bit off in the shared application reference. Instead of

       # This references a component `httpserver` in manifest `network`
       # Other manifest must be annotated with `wasmcloud.dev/shared=true`
       #     or this will be rejected at put time.
       manifest:
         name: network
         component: httpserver

it should probably be

       # This references a component `httpserver` in manifest `network`
       # Other manifest must be annotated with `wasmcloud.dev/shared=true`
       #     or this will be rejected at put time.
       application:
         name: network
         component: httpserver

Applications are what we're all working with, manifests are just the YAML version of them

@protochron I think this makes sense, and it likely means that we can fail a bit earlier in the deployment process if it's obvious you should be looking for a running application 🤔 I'll make this change in my WIP PR

Just wanted to add use cases here to further motivate this work...

There is a whole class of problems this would solve, where instead of having providers as proxies to singleton non-wasmCloud deployments of supporting software (reverse proxy, database, cache), the wasmCloud providers would become the singleton deployments. Eliminating the need for supporting software outside of wasmCloud means I can get rid of k8s or other managed services.

Example 1: a singleton HTTP server provider that multiple components can contribute endpoint handlers to (reverse proxy extensibility)
Example 2: a singleton cache provider that multiple components can get/set from
Example 3: a singleton pub-sub provider that components can exchange application messages on
Example 4: a singleton embedded database provider that components can contribute tables to

@atifsyedali thank you for the additional use cases, these will be helpful when setting up tests & edge cases in #381 !