How odo works
How odo dev
works
In a nutshell, when running odo dev
:
odo
reads and validates the Devfile in the current directory. For example, it makes sure acommand
of the right kind (run
when runningodo dev
, ordebug
when runningodo dev --debug
) is defined.odo
creates 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,
odo
executes 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 therun
ordebug
command defined. odo
reacts to events occurring in the cluster that might affect the resources managed.odo
watches 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,odo
may 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 pressingp
at any time. See the command reference onodo dev
for more details.odo
optionally rebuilds and restarts the running application if the commands are not marked ashotReloadCapable
in the Devfile. If the Build of Run command is marked ashotReloadCapable
, the application is supposed to handle source code changes on its own; soodo
does not run this command again. Otherwise,odo
rebuilds 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.odo
then sets up port-forwarding for each endpoint declared in the Devfile, and reports the local port in its output.- When
odo dev
is 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.deployment
field 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
command
totail
. This assumes that the container image (set in the Devfile) contains thetail
executable. - the container
args
to[-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
ephemeral
is set tofalse
, which is the default value,odo
creates a PersistentVolumeClaim (PVC) (with the default storage class). - If
ephemeral
is set totrue
,odo
translates it into anemptyDir
volume, 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
mountSources
flag (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: true
in order to synchronize files. - the type of volume created depends on the configuration of
odo
, and more specifically on the value of theEphemeral
setting:- if
Ephemeral
isfalse
, which is the default setting,odo
creates a PersistentVolumeClaim (PVC) (with the default storage class) - if
Ephemeral
istrue
,odo
creates anemptyDir
volume, 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
.odoignore
file, or, if this file is not present, in the.gitignore
file.dev.odo.push.path:target
attributes are also considered to push only selected files. The directory.git
is not synchronized by default. If you add the--sync-git-dir
flag to theodo dev
command, the.git
repository will be synchronized to the container, regardless of its presence in.odoignore
or.gitignore
. 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: true
andEphemeral
preference set tofalse
(default value):
Devfile | Kubernetes Deployment | |
| => |
|
- with
mountSources: true
andEphemeral
setting set totrue
:
Devfile | Kubernetes Deployment | |
| => |
|
- with
mountSources: false
andEphemeral
preference set tofalse
. Note that odo requires at least one component in the Devfile to setmountSources: true
in order to synchronize files.
Devfile | Kubernetes Deployment | |
| => |
|
- with
mountSources: false
andEphemeral
preference set totrue
. Note that odo requires at least one component in the Devfile to setmountSources: true
in 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
Ephemeral
Setting 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
volume
component:
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