How odo works
How odo dev works
In a nutshell, when running odo dev:
odoreads and validates the Devfile in the current directory. For example, it makes sure acommandof the right kind (runwhen runningodo dev, ordebugwhen runningodo dev --debug) is defined.odocreates resources in the cluster and manages them. Specifically, it creates the following resources:- Deployment for running the containers. See the section on Deployment for further details.
- Service for accessibility. See the section on Service for further details.
- Once the resources are ready,
odoexecutes anybuild(optional) andrun(ordebug) commands defined in the Devfile into the dedicated containers. It then maintains a connection to the process launched inside the container, and representing therunordebugcommand defined. odoreacts to events occurring in the cluster that might affect the resources managed.odowatches for local changes and synchronizes changed files into the running container, unless told otherwise (when runningodo dev --no-watch). If the local Devfile is modified,odomay need to change the resources it previously created, which might result in recreating the running containers. Note that synchronization and push to the cluster can also be triggered on demand by pressingpat any time. See the command reference onodo devfor more details.odooptionally rebuilds and restarts the running application if the commands are not marked ashotReloadCapablein the Devfile. If the Build of Run command is marked ashotReloadCapable, the application is supposed to handle source code changes on its own; soododoes not run this command again. Otherwise,odorebuilds the application then restarts the running application by stopping the process started previously, then executes the command again in the container. Again, it maintains a connection to that process as long as it is running in the container.odothen sets up port-forwarding for each endpoint declared in the Devfile, and reports the local port in its output.- When
odo devis stopped viaCtrl+C, it deletes all the resources created previously and stops port-forwarding and code synchronization.
It is strongly discouraged to run multiple odo dev processes in parallel from the same component directory.
Otherwise, such processes will compete with each other in trying to manage the same Kubernetes resources,
and you would end up with several instances of port-forwarding and code synchronization.
How odo dev translates a container component into Kubernetes resources
Given a component (aptly named my-component-name in the metadata.name field) in the Devfile excerpt below:
metadata:
name: my-component-name
odo will create the following Kubernetes resources in the current namespace:
- a Deployment named
my-component-name-app - a Service named
my-component-name-app
Per the Devfile specification, the metadata.name field is optional.
If it is not defined, odo will try to autodetect the name from the project source code (based on information from files like package.json or pom.xml).
As a last resort, it will use the current directory name.
Resource Labels
By default, odo adds the following labels to all the resources it creates:
You can find more information about some of those common labels in the Openshift and Kubernetes documentations.
| Key | Description | Example Value |
|---|---|---|
app | the application; always app. | app |
app.kubernetes.io/instance | the component name. | my-component-name |
app.kubernetes.io/managed-by | the tool used to create this resource; always odo. | odo |
app.kubernetes.io/managed-by-version | the version of the odo binary used to create this resource. | v3.0.0 |
app.kubernetes.io/part-of | the higher-level application using this resource; always app. | app |
app.openshift.io/runtime | the application runtime, if available. Value is read in order from the metadata.projectType or metadata.language fields in the Devfile. As both metadata are optional, this annotation can be omitted. | spring |
component | the component name. | my-component-name |
odo.dev/mode | in which mode the component is running. Possible values: Dev (if running odo dev), Deploy (if running odo deploy). | Dev |
Deployment
odo will create a Deployment with the characteristics below.
Annotations
By default, odo adds the following annotations to the Deployment:
| Key | Description | Example Value |
|---|---|---|
odo.dev/project-type | the application runtime, if available. Value is read in order from the metadata.projectType or metadata.language fields in the Devfile. As both metadata are optional, this annotation can be omitted. | spring |
alpha.image.policy.openshift.io/resolve-names | Enable the use of ImageStreams on OpenShift | * |
Notes:
- Any additional annotations defined via the
components[].container.annotation.deploymentfield will also be added to this resource. - All those annotations are also added to the underlying Pods managed by this Deployment.
Example
| Devfile | Kubernetes Deployment | |
| => | |
Labels
By default, odo adds the labels mentioned in the Resource Labels section.
Note that the same labels are added to the underlying Pods managed by this Deployment.
Example
| Devfile | Kubernetes Deployment | |
| => | |
Replicas
The number of Replicas for this Deployment is explicitly set to 1 and is expected to always have this value.
Security Context
odo determines by looking at the labels set in the current namespace if Pod Security Standards have to be respected for the deployed pod.
If necessary, odo will add Security Context restrictions to the Pod definition to make the Pod admissible.
Pods and Containers
Each components[].container block is translated into a dedicated container definition in the Pod template.
Environment variables
Each entry in the components[].container.env section translates into the same environment variable in the corresponding Pod container.
Additionally, the following environment variables are reserved and injected into the container definition if mountSources is defined as true for the component's container:
| Key | Description | Example Value |
|---|---|---|
PROJECTS_ROOT | A path where the project sources are mounted as defined by container component's sourceMapping. Default value is /projects. | /projects |
PROJECT_SOURCE | A path to a project source ($PROJECTS_ROOT/). If there are multiple projects, this will point to the directory of the first one. Default value is /projects. | /projects |
Example
| Devfile | Kubernetes Deployment | |
| => | |
Command and Args
odo will use the specified components[].container.command or components[].container.args fields as is
for the Kubernetes container command and args definitions.
The only requirement is that those fields should result in a non-terminating container,
so odo can execute the commands it needs to manage the application.
If the container is terminating, the Deployment will not reach the desired state, and odo will not be able to run the commands and start the application.
If both fields are missing, odo defaults to setting:
- the container
commandtotail. This assumes that the container image (set in the Devfile) contains thetailexecutable. - the container
argsto[-f, /dev/null]
Example
| Devfile | Kubernetes Deployment | |
| => | |
| => | |
Image Pull Policy
At this time, the image pull policy for all containers is fixed to Always and cannot be modified.
Resources Limits and Requests
odo maps each components[].container.{cpu,memory}{Limit,Request} to corresponding resources.{limits,requests}.{cpu,memory} fields with the respective values.
Example
| Devfile | Kubernetes Deployment | |
| => | |
Ports
odo translates each element in the components[].container.endpoints[] block into a dedicated containerPort with the same name and port, regardless of the exposure.
Example
| Devfile | Kubernetes Deployment | |
| => | |
Volumes
odo creates the following volumes and mounts them in the containers:
| Volume name | Volume Type | Mount Path | Description |
|---|---|---|---|
odo-shared-data | emptyDir | /opt/odo | Internal Purpose. Contains files (like PIDs for commands) necessary for odo. |
Devfile Volume Components
The Devfile specification allows to define volume components to share files among container components.
Such volume components can be marked as ephemeral or not.
- If
ephemeralis set tofalse, which is the default value,odocreates a PersistentVolumeClaim (PVC) (with the default storage class). - If
ephemeralis set totrue,odotranslates it into anemptyDirvolume, tied to the lifetime of the Pod.
Example
| Devfile | Kubernetes Deployment | |
| => | |
Project Sources
As mentioned in how odo dev works, odo is able to perform a one-way synchronization of the local source code, i.e., from the developer machine to the development pod running in the cluster.
This is done via a Volume, named odo-projects, mounted in the container.
However, this is subject to three things:
- the value of the
mountSourcesflag (default value istrue) in the Devfile container component. Project sources are not mounted in the container if this is set tofalse. Note that odo requires at least one component in the Devfile to setmountSources: truein order to synchronize files. - the type of volume created depends on the configuration of
odo, and more specifically on the value of theEphemeralsetting:- if
Ephemeralisfalse, which is the default setting,odocreates a PersistentVolumeClaim (PVC) (with the default storage class) - if
Ephemeralistrue,odocreates anemptyDirvolume, tied to the lifetime of the Pod.
- if
- the complete content of the current directory and its sub-directories is pushed to the container, except the files listed in the
.odoignorefile, or, if this file is not present, in the.gitignorefile.dev.odo.push.path:targetattributes are also considered to push only selected files. See Pushing Source Files for more details.
| Volume name | Volume Type | Mount Path | Description |
|---|---|---|---|
odo-projects | PersistentVolumeClaim (PVC) if Ephemeral preference is false, emptyDir otherwise. | Value of component[].container.sourceMapping (default value is /projects). | Used for project source code synchronization. |
Examples
- with
mountSources: trueandEphemeralpreference set tofalse(default value):
| Devfile | Kubernetes Deployment | |
| => | |
- with
mountSources: trueandEphemeralsetting set totrue:
| Devfile | Kubernetes Deployment | |
| => | |
- with
mountSources: falseandEphemeralpreference set tofalse. Note that odo requires at least one component in the Devfile to setmountSources: truein order to synchronize files.
| Devfile | Kubernetes Deployment | |
| => | |
- with
mountSources: falseandEphemeralpreference set totrue. Note that odo requires at least one component in the Devfile to setmountSources: truein order to synchronize files.
| Devfile | Kubernetes Deployment | |
| => | |
Service
odo will create a Service of type ClusterIP with the characteristics below.
Annotations
By default, odo adds the following annotations to the Service:
| Key | Value | Description |
|---|---|---|
service.binding/backend_ip | path={.spec.clusterIP} | exposes the Service clusterIP address as binding data, so that this can be used as a backing service via the Service Binding Operator (SBO). More details on SBO documentation. |
service.binding/backend_port | path={.spec.ports},elementType=sliceOfMaps,sourceKey=name,sourceValue=port | exposes the Service ports as binding data, so this can be used as a backing service via the Service Binding Operator (SBO). More details on SBO documentation. |
See this blog post for more details about binding external services.
Note that any additional annotations defined via the components[].container.annotation.service Devfile field will also be added to this resource.
Example
| Devfile | Kubernetes Service | |
| => | |
Labels
By default, odo adds the labels mentioned in the Resource Labels section.
Example
| Devfile | Kubernetes Service | |
| => | |
Ports
For each endpoint with an exposure other than none defined in the components[].container.endpoints Devfile block,
odo adds a port to the Service spec.
Example
| Devfile | Kubernetes Service | |
| => | |
Full example
Example of Devfile and resulting Kubernetes resources
Given this Devfile:
schemaVersion: 2.2.0
metadata:
description: Spring Boot® using Java
displayName: Spring Boot®
globalMemoryLimit: 2674Mi
icon: https://spring.io/images/projects/spring-edf462fec682b9d48cf628eaf9e19521.svg
language: java
name: my-sample-java-springboot
projectType: spring
tags:
- Java
- Spring
version: 1.1.0
commands:
- exec:
commandLine: mvn clean -Dmaven.repo.local=/home/user/.m2/repository package -Dmaven.test.skip=true
component: tools
group:
isDefault: true
kind: build
workingDir: ${PROJECT_SOURCE}
id: build
- exec:
commandLine: mvn -Dmaven.repo.local=/home/user/.m2/repository spring-boot:run
component: tools
group:
isDefault: true
kind: run
workingDir: ${PROJECT_SOURCE}
id: run
- exec:
commandLine: java -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=${DEBUG_PORT},suspend=n
-jar target/*.jar
component: tools
group:
isDefault: true
kind: debug
workingDir: ${PROJECT_SOURCE}
id: debug
components:
- container:
annotation:
deployment:
example.com/my-deploy-annotation1: my-deploy-annotation-val1
service:
example.com/my-svc-annotation1: my-svc-annotation-val1
endpoints:
- name: http-springboot
targetPort: 8080
- name: debug
targetPort: 5858
exposure: none
env:
- name: DEBUG_PORT
value: "5858"
image: quay.io/eclipse/che-java11-maven:next
memoryLimit: 768Mi
mountSources: true
volumeMounts:
- name: m2
path: /home/user/.m2
name: tools
- container:
annotation:
deployment:
example.com/my-deploy-annotation-echo1: my-deploy-annotation-val1
service:
example.com/my-svc-annotation-echo1: my-svc-annotation-val1
endpoints:
- name: echo-ep1
targetPort: 18080
env:
- name: MY_ENV_VAR
value: "some value"
image: alpine:latest
mountSources: false
command: [tail]
args: [-f, /dev/null]
name: echo-container
- name: m2
volume:
size: 3Gi
odo will generate the following Kubernetes Resources:
- Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
example.com/my-deploy-annotation-echo1: my-deploy-annotation-val1
example.com/my-deploy-annotation1: my-deploy-annotation-val1
odo.dev/project-type: spring
labels:
app: app
app.kubernetes.io/instance: my-sample-java-springboot
app.kubernetes.io/managed-by: odo
app.kubernetes.io/managed-by-version: v3.0.0
app.kubernetes.io/part-of: app
app.openshift.io/runtime: spring
component: my-sample-java-springboot
odo.dev/mode: Dev
name: my-sample-java-springboot-app
namespace: default
spec:
replicas: 1
selector:
matchLabels:
component: my-sample-java-springboot
strategy:
type: Recreate
template:
metadata:
annotations:
example.com/my-deploy-annotation-echo1: my-deploy-annotation-val1
example.com/my-deploy-annotation1: my-deploy-annotation-val1
odo.dev/project-type: spring
labels:
app: app
app.kubernetes.io/instance: my-sample-java-springboot
app.kubernetes.io/managed-by: odo
app.kubernetes.io/managed-by-version: v3.0.0
app.kubernetes.io/part-of: app
app.openshift.io/runtime: spring
component: my-sample-java-springboot
odo.dev/mode: Dev
name: my-sample-java-springboot-app
namespace: default
spec:
containers:
- args:
- -f
- /dev/null
command:
- tail
env:
- name: DEBUG_PORT
value: "5858"
- name: PROJECTS_ROOT
value: /projects
- name: PROJECT_SOURCE
value: /projects
image: quay.io/eclipse/che-java11-maven:next
imagePullPolicy: Always
name: tools
ports:
- containerPort: 8080
name: http-springboot
protocol: TCP
- containerPort: 5858
name: debug
protocol: TCP
resources:
limits:
memory: 768Mi
volumeMounts:
- mountPath: /projects
name: odo-projects
- mountPath: /opt/odo/
name: odo-shared-data
- mountPath: /home/user/.m2
name: m2-my-sample-java-springboot-app-vol
- args:
- -f
- /dev/null
command:
- tail
env:
- name: MY_ENV_VAR
value: some value
image: alpine:latest
imagePullPolicy: Always
name: echo-container
ports:
- containerPort: 18080
name: echo-ep1
protocol: TCP
resources: {}
volumeMounts:
- mountPath: /opt/odo/
name: odo-shared-data
restartPolicy: Always
securityContext: {}
volumes:
- name: m2-my-sample-java-springboot-app-vol
persistentVolumeClaim:
claimName: m2-my-sample-java-springboot-app
- name: odo-projects
persistentVolumeClaim:
claimName: odo-projects-my-sample-java-springboot-app
- emptyDir: {}
name: odo-shared-data
- Service:
apiVersion: v1
kind: Service
metadata:
annotations:
example.com/my-svc-annotation-echo1: my-svc-annotation-val1
example.com/my-svc-annotation1: my-svc-annotation-val1
service.binding/backend_ip: path={.spec.clusterIP}
service.binding/backend_port: path={.spec.ports},elementType=sliceOfMaps,sourceKey=name,sourceValue=port
labels:
app: app
app.kubernetes.io/instance: my-sample-java-springboot
app.kubernetes.io/managed-by: odo
app.kubernetes.io/managed-by-version: v3.0.0
app.kubernetes.io/part-of: app
app.openshift.io/runtime: spring
component: my-sample-java-springboot
odo.dev/mode: Dev
namespace: default
name: my-sample-java-springboot-app
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
kind: Deployment
name: my-sample-java-springboot-app
spec:
ports:
- name: http-springboot
port: 8080
protocol: TCP
targetPort: 8080
- name: echo-ep1
port: 18080
protocol: TCP
targetPort: 18080
selector:
component: my-sample-java-springboot
sessionAffinity: None
type: ClusterIP
- PersistentVolumeClaim for the project source code (because of
EphemeralSetting set tofalse(default)):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
finalizers:
- kubernetes.io/pvc-protection
labels:
app: app
app.kubernetes.io/instance: my-sample-java-springboot
app.kubernetes.io/managed-by: odo
app.kubernetes.io/managed-by-version: v3.0.0
app.kubernetes.io/part-of: app
app.kubernetes.io/storage-name: odo-projects
app.openshift.io/runtime: spring
component: my-sample-java-springboot
odo-source-pvc: odo-projects
odo.dev/mode: Dev
storage-name: odo-projects
name: odo-projects-my-sample-java-springboot-app
namespace: default
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
kind: Deployment
name: my-sample-java-springboot-app
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
- PersistentVolumeClaim for the non-ephemeral
volumecomponent:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
finalizers:
- kubernetes.io/pvc-protection
labels:
app: app
app.kubernetes.io/instance: my-sample-java-springboot
app.kubernetes.io/managed-by: odo
app.kubernetes.io/managed-by-version: v3.0.0
app.kubernetes.io/part-of: app
app.kubernetes.io/storage-name: m2
app.openshift.io/runtime: spring
component: my-sample-java-springboot
odo.dev/mode: Dev
storage-name: m2
name: m2-my-sample-java-springboot-app
namespace: default
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
kind: Deployment
name: my-sample-java-springboot-app
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi