|
| 1 | +# Pluggable components injector |
| 2 | + |
| 3 | +- Author(s): Marcos Candeia (@mcandeia) |
| 4 | +- State: Ready for Implementation |
| 5 | +- Updated: 11/21/2022 |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +Pluggable components are components that are not included as part of the runtime, as opposed to built-in ones that are included. The major difference between pluggable components and built-in components is the operational burden related to bootstrap/start the pluggable component process that are not necessary when using a built-in one since they run in the same process as Dapr runtime. This operational burden is present in many ways when using pluggable components and can lead to errors and hard debugging. In addition, there are certain configurations that are tied to the Dapr and how the runtime registers the pluggable component that is repetitive and can be better handled by Dapr instead of delegating this responsibility to the end-user. This proposal suggest the addition of a new mode of execution for selected pluggable components: injectable pluggable components. |
| 10 | + |
| 11 | +## Background |
| 12 | + |
| 13 | +#### Decrease the operational burden |
| 14 | + |
| 15 | +Even considering the new pluggable components annotation from [#5402](https://github.com/dapr/dapr/issues/5402), setting up applications to properly work with pluggable components still not an easy task due to the operational related to bootstrapping containers over and over again for each application that the user needs, especially if you consider that components are often not well [scoped](https://docs.dapr.io/operations/components/component-scopes/). Without scope, a component make itself available for all applications within the same namespace, meaning that every deployment/pod should re-do the same manual job of mounting volumes, declaring environment variables and pinning container images. |
| 16 | + |
| 17 | +So let's say you have an application named `my-app` and, another one named `my-app-2`, your two deployments/pods will look like the following: |
| 18 | + |
| 19 | +```yaml |
| 20 | +apiVersion: apps/v1 |
| 21 | +kind: Deployment |
| 22 | +metadata: |
| 23 | +name: app |
| 24 | +labels: |
| 25 | + app: app |
| 26 | +spec: |
| 27 | +replicas: 1 |
| 28 | +selector: |
| 29 | + matchLabels: |
| 30 | + app: app |
| 31 | +template: |
| 32 | + metadata: |
| 33 | + labels: |
| 34 | + app: app |
| 35 | + annotations: |
| 36 | + dapr.io/pluggable-components: "component" |
| 37 | + dapr.io/app-id: "my-app" |
| 38 | + dapr.io/enabled: "true" |
| 39 | + spec: |
| 40 | + volumes: |
| 41 | + - name: my-component-required-volume |
| 42 | + emptyDir: {} |
| 43 | + containers: |
| 44 | + - name: my-app |
| 45 | + image: my-app-image:latest |
| 46 | + ### This is the pluggable component container. |
| 47 | + - name: component |
| 48 | + image: component:v1.0.0 |
| 49 | + volumes: |
| 50 | + - name: my-component-required-volume |
| 51 | + mountPath: "/my-data" |
| 52 | + env: |
| 53 | + - name: MY_ENV_VAR_NAME |
| 54 | + value: MY_ENV_VAR_VALUE |
| 55 | + |
| 56 | +--- |
| 57 | +apiVersion: apps/v1 |
| 58 | +kind: Deployment |
| 59 | +metadata: |
| 60 | +name: app-2 |
| 61 | +labels: |
| 62 | + app: app-2 |
| 63 | +spec: |
| 64 | +replicas: 1 |
| 65 | +selector: |
| 66 | + matchLabels: |
| 67 | + app: app-2 |
| 68 | +template: |
| 69 | + metadata: |
| 70 | + labels: |
| 71 | + app: app-2 |
| 72 | + annotations: |
| 73 | + dapr.io/pluggable-components: "component" |
| 74 | + dapr.io/app-id: "my-app-2" |
| 75 | + dapr.io/enabled: "true" |
| 76 | + spec: |
| 77 | + volumes: |
| 78 | + - name: my-component-required-volume |
| 79 | + emptyDir: {} |
| 80 | + containers: |
| 81 | + - name: my-app-2 |
| 82 | + image: my-app-2-image:latest |
| 83 | + ### This is the pluggable component container. |
| 84 | + - name: component |
| 85 | + image: component:v1.0.0 |
| 86 | + volumes: |
| 87 | + - name: my-component-required-volume |
| 88 | + mountPath: "/my-data" |
| 89 | + env: |
| 90 | + - name: MY_ENV_VAR_NAME |
| 91 | + value: MY_ENV_VAR_VALUE |
| 92 | +``` |
| 93 | +
|
| 94 | +Notice that everything related to the pluggable component container is repeated, and if you have a third application that doesn't require your pluggable component to work, so you have to scope your component to be initialized with only these two declared deployments/pods. |
| 95 | +
|
| 96 | +```yaml |
| 97 | +apiVersion: dapr.io/v1alpha1 |
| 98 | +kind: Component |
| 99 | +metadata: |
| 100 | + name: my-component |
| 101 | +spec: |
| 102 | + type: state.my-component |
| 103 | + version: v1 |
| 104 | + metadata: [] |
| 105 | +scopes: |
| 106 | + - "my-app" |
| 107 | + - "my-app-2" |
| 108 | +``` |
| 109 | +
|
| 110 | +For each deployment that you have to add in your cluster, if that requires such pluggable component, you must also add in the scope list of the component spec, which ends up being error prone and intrusive. |
| 111 | +
|
| 112 | +#### Component spec atomicity/self-contained |
| 113 | +
|
| 114 | +Allow interchangeable/swappable components are one of the top amazing features that we provide, with that, a user can, in runtime, swap out a component with the same interface for another. Pluggable components made this behavior more difficult to maintain as it requires coordination, for a small time window, the user must provide a way to Dapr access both components at same time, otherwise it becomes very difficult to orchestrate that change manually. |
| 115 | +To exemplify, suppose that we want to replace the Redis PubSub with the Kafka PubSub, and they are pluggable components. This is not only a matter of replacing the component spec itself, but it will require orchestrating the related deployments, otherwise it would lead in having an application pointing out to Kafka but with no Kafka pluggable component running and vice-versa. |
| 116 | +
|
| 117 | +The following diagram is exemplifying how that orchestrated change must applied: |
| 118 | +
|
| 119 | +<img width="466" alt="image" src="https://user-images.githubusercontent.com/5839364/201184828-d4e7357b-716a-4a3b-b7a5-dd22d1be7cda.png"> |
| 120 | +
|
| 121 | +> That can't be avoided in scenarios where Dapr is not present as an orchestrator, for instance, self-hosted mode, but there are platforms that supports extensibility for orchestrating applications and its dependencies, like Kubernetes. |
| 122 | +
|
| 123 | +re: You can argue that Kubernetes solve this scenario by reconciling the cluster state until it succeeds, but still, it severely degrade the user experience when requires additional knowledge to build their applications with Dapr. |
| 124 | +
|
| 125 | +## Related Items |
| 126 | +
|
| 127 | +### Related proposals |
| 128 | +
|
| 129 | +[Pluggable components Annotations](https://github.com/dapr/dapr/issues/5402) |
| 130 | +
|
| 131 | +### Related issues |
| 132 | +
|
| 133 | +N/A |
| 134 | +
|
| 135 | +## Expectations and alternatives |
| 136 | +
|
| 137 | +### What is in scope for this proposal? |
| 138 | +
|
| 139 | +This proposal aims to add a new execution mode for pluggable components, the dapr-injected pluggable components. |
| 140 | +
|
| 141 | +### What is deliberately _not_ in scope? |
| 142 | +
|
| 143 | +This proposal does not aims to manage users' pluggable components code. The goal here is to provide a better UX when using pluggable components while decrease the operation burden. |
| 144 | +
|
| 145 | +## Implementation Details |
| 146 | +
|
| 147 | +### Design |
| 148 | +
|
| 149 | +This proposal aims to add a new execution mode for pluggable components, the dapr-injected pluggable components, that makes the operational behind remarkable like the built-in components. The operational burden is still present somewhere but divided into small reusable pieces. |
| 150 | +
|
| 151 | +<meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-69570229-7fff-318a-575c-cff928d2ef5b"><p dir="ltr" style="line-height:1.38;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;"> </span></p><div dir="ltr" style="margin-left:0pt;" align="left"> |
| 152 | +
|
| 153 | +| Type | Injected by Dapr | Managed by User/Unmanaged | |
| 154 | +| ----------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | |
| 155 | +| Configuration | Dapr Injects env vars and mount the shared volumes | The user manually mounts and declares shared volumes | |
| 156 | +| Container updates | Dapr automatically detects and applies, rolling out changes based on declared components | Users must redeploy their applications with the new desired version | |
| 157 | +| Persona | Cluster operator/End user | End user | |
| 158 | +| Scope | Does not need to be scoped | If not scoped, all applications should have deployed the pluggable component, otherwise runtime errors might happen | |
| 159 | +
|
| 160 | +</div></b> |
| 161 | +
|
| 162 | +#### Component spec annotations |
| 163 | +
|
| 164 | +The component spec is still the entry point for all component types being pluggable or not, given that the pluggable components are a subset of all users declared components, even more, the pluggable components can be inferred from the declared components, we can actually leverage that property to extend our component spec, by adding custom annotations to allow Dapr to inject the component container at the time the Injector is also injecting the Dapr sidecar container. |
| 165 | +
|
| 166 | +Example: |
| 167 | +
|
| 168 | +```yaml |
| 169 | +apiVersion: dapr.io/v1alpha1 |
| 170 | +kind: Component |
| 171 | +metadata: |
| 172 | + name: my-component |
| 173 | + annotations: |
| 174 | + dapr.io/component-container-image: "component:v1.0.0" |
| 175 | +spec: |
| 176 | + type: state.my-componentƒ |
| 177 | + version: v1 |
| 178 | + metadata: [] |
| 179 | +``` |
| 180 | +
|
| 181 | +Optionally you can mount volumes and add env variables into the containers by using the `dapr.io/component-container-volume-mounts(-rw)` and `dapr.io/component-container-env` annotations. |
| 182 | + |
| 183 | +```yaml |
| 184 | +apiVersion: dapr.io/v1alpha1 |
| 185 | +kind: Component |
| 186 | +metadata: |
| 187 | + name: my-component |
| 188 | + annotations: |
| 189 | + dapr.io/component-container-image: "component:v1.0.0" |
| 190 | + dapr.io/component-container-volume-mounts: "volume-name:/volume-path,volume-name-2:/volume-path-2" # read-only, "$VOLUME_NAME:$VOLUME_PATH,$VOLUME_NAME_2:$VOLUME_PATH2" |
| 191 | + dapr.io/component-container-volume-mounts-rw: "volume-name-rw:/volume-path-rw,volume-name-2-rw:/volume-path-2-rw" # read-write "$VOLUME_NAME:$VOLUME_PATH,$VOLUME_NAME_2:$VOLUME_PATH2" |
| 192 | + dapr.io/component-container-env: "env-var=env-var-value,env-var-2=env-var-value-2" #optional "$ENV_NAME=$ENV_VALUE,$ENV_NAME_2=$ENV_VALUE_2" |
| 193 | +spec: |
| 194 | + type: state.my-component |
| 195 | + version: v1 |
| 196 | + metadata: [] |
| 197 | +``` |
| 198 | + |
| 199 | +By default the injector creates undeclared volumes as `emptyDir` volumes, if you want a different volume type you should declare it by yourself in your pods. |
| 200 | + |
| 201 | +#### Pod annotations |
| 202 | + |
| 203 | +In order to allow users to turn off the component injector for their pod, a new annotation will be available, similar to the one that we have for enabling dapr: `dapr.io/inject-pluggable-components:"true"`. Let's rewrite the previous examples using the injected pluggable components feature, it would be something like: |
| 204 | + |
| 205 | +The apps deployments/pods: |
| 206 | + |
| 207 | +```yaml |
| 208 | +apiVersion: apps/v1 |
| 209 | +kind: Deployment |
| 210 | +metadata: |
| 211 | +name: app |
| 212 | +labels: |
| 213 | + app: app |
| 214 | +spec: |
| 215 | +replicas: 1 |
| 216 | +selector: |
| 217 | + matchLabels: |
| 218 | + app: app |
| 219 | +template: |
| 220 | + metadata: |
| 221 | + labels: |
| 222 | + app: app |
| 223 | + annotations: |
| 224 | + dapr.io/inject-pluggable-components: "true" |
| 225 | + dapr.io/app-id: "my-app" |
| 226 | + dapr.io/enabled: "true" |
| 227 | + spec: |
| 228 | + containers: |
| 229 | + - name: my-app |
| 230 | + image: my-app-image:latest |
| 231 | +--- |
| 232 | +apiVersion: apps/v1 |
| 233 | +kind: Deployment |
| 234 | +metadata: |
| 235 | +name: app-2 |
| 236 | +labels: |
| 237 | + app: app-2 |
| 238 | +spec: |
| 239 | +replicas: 1 |
| 240 | +selector: |
| 241 | + matchLabels: |
| 242 | + app: app-2 |
| 243 | +template: |
| 244 | + metadata: |
| 245 | + labels: |
| 246 | + app: app-2 |
| 247 | + annotations: |
| 248 | + dapr.io/inject-pluggable-components: "true" |
| 249 | + dapr.io/app-id: "my-app-2" |
| 250 | + dapr.io/enabled: "true" |
| 251 | + spec: |
| 252 | + containers: |
| 253 | + - name: my-app-2 |
| 254 | + image: my-app-2-image:latest |
| 255 | +``` |
| 256 | + |
| 257 | +And the component spec: |
| 258 | + |
| 259 | +```yaml |
| 260 | +apiVersion: dapr.io/v1alpha1 |
| 261 | +kind: Component |
| 262 | +metadata: |
| 263 | + name: my-component |
| 264 | + annotations: |
| 265 | + dapr.io/component-container-image: "component:v1.0.0" |
| 266 | + dapr.io/component-container-volume-mounts: "my-component-required-volume;/my-data" |
| 267 | + dapr.io/component-container-env: "MY_ENV_VAR_NAME;MY_ENV_VAR_VALUE" |
| 268 | +spec: |
| 269 | + type: state.my-component |
| 270 | + version: v1 |
| 271 | + metadata: [] |
| 272 | +``` |
| 273 | + |
| 274 | +### Feature lifecycle outline |
| 275 | + |
| 276 | +#### Expectations |
| 277 | + |
| 278 | +The feature is expected to be delivered as part of dapr/dapr v1.10.0 as a preview feature together with the new pluggable components SDK. |
| 279 | + |
| 280 | +#### Compatability guarantees |
| 281 | + |
| 282 | +Pluggable components that has been used will not be affected by this. |
| 283 | + |
| 284 | +#### Deprecation / co-existence with existing functionality |
| 285 | + |
| 286 | +N/A |
| 287 | + |
| 288 | +### Acceptance Criteria |
| 289 | + |
| 290 | +N/A |
| 291 | + |
| 292 | +## Completion Checklist |
| 293 | + |
| 294 | +What changes or actions are required to make this proposal complete? Some examples: |
| 295 | + |
| 296 | +- [] Change the sidecar injector to make requests to the operator for listing components (or list it using its own role) |
| 297 | +- [] Add 1 more annotation for pods `dapr.io/inject-pluggable-components: "true"` and 3 more for components `dapr.io/component-container-image`, `dapr.io/component-container-env` and `dapr.io/component-container-volume-mounts` |
| 298 | +- [] Add the components container injector based on declared components |
0 commit comments