odo v3, a new version of odo based on devfiles
Published: 2022-05-02 13:51:52

odo is a tool that aims to simplify the life of developers working on cloud-native applications.

Thanks to the emergence of the [devfile](https://devfile.io/) open standard, which  has been accepted as a CNCF Sandbox project recently (in January 2022), odo v3 is now entirely based on this open standard.

The goal of the devfile standard is to define the structure of applications and how developers can work on them.

A single devfile defines the smallest building block of an application, that a developer can build, run, test, debug and deploy. In a cloud-native environment, we generally talk about a micro-service. 

Firstly, the devfile describes the container that is needed to be deployed on a cluster during the development phases, along with the commands to execute on this container to build, run, test and debug the program, assuming the sources have been synchronized into the container.

Secondly, the devfile provides the instructions to build the container image ready for production, along with the Kubernetes resources to deploy to the cluster.

## An example of devfile

To illustrate, here is a simple yet complete devfile, usable for a NodeJS micro-service:

```yaml
schemaVersion: 2.2.0
metadata:
  description: Stack with NodeJS 12
  displayName: NodeJS Runtime
  language: nodejs
  name: my-nodejs-app
  projectType: nodejs
variables:
  CONTAINER_IMAGE: quay.io/phmartin/myimage
components:
- name: runtime
  container:
    endpoints:
    - name: http-3000
      targetPort: 3000
    - name: debug-5858
      targetPort: 5858
    image: registry.access.redhat.com/ubi8/nodejs-14:latest
    memoryLimit: 1024Mi
    mountSources: true
    sourceMapping: /project
- name: outerloop-build
  image:
    dockerfile:
      buildContext: ${PROJECT_ROOT}
      rootRequired: false
      uri: ./Dockerfile
    imageName: "{{CONTAINER_IMAGE}}"
- name: outerloop-deployment
  kubernetes:
    inlined: |
      kind: Deployment
      apiVersion: apps/v1
      metadata:
        name: my-node
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: node-app
        template:
          metadata:
            labels:
              app: node-app
          spec:
            containers:
              - name: my-node
                image: {{CONTAINER_IMAGE}}
                ports:
                  - name: http
                    containerPort: 3001
                    protocol: TCP
                resources:
                  limits:
                    memory: "1024Mi"
                    cpu: "500m"
- name: outerloop-service
  kubernetes:
    inlined: |
      apiVersion: v1
      kind: Service
      metadata:
        name: svc
      spec:
        ports:
        - name: "3000"
          port: 3000
          protocol: TCP
          targetPort: 3000
        selector:
          app: node-app
        type: ClusterIP
commands:
- id: install
  exec:
    commandLine: npm install
    component: runtime
    group:
      isDefault: true
      kind: build
    workingDir: /project
- id: run
  exec:
    commandLine: npm start
    component: runtime
    group:
      isDefault: true
      kind: run
    workingDir: /project
- id: debug
  exec:
    commandLine: npm run debug
    component: runtime
    group:
      isDefault: true
      kind: debug
    workingDir: /project
- id: test
  exec:
    commandLine: npm test
    component: runtime
    group:
      isDefault: true
      kind: test
    workingDir: /project
- id: deploy
  composite:
    commands:
    - build-image
    - k8s-deployment
    - k8s-service
    group:
      isDefault: true
      kind: deploy
- id: build-image
  apply:
    component: outerloop-build
- id: k8s-deployment
  apply:
    component: outerloop-deployment
- id: k8s-service
  apply:
    component: outerloop-service
starterProjects:
- name: nodejs-starter
  git:
    remotes:
      origin: https://github.com/odo-devfiles/nodejs-ex.git
```

The `runtime` component defines the container that will be deployed to support the program in development. Specifically, it will use the image `registry.access.redhat.com/ubi8/nodejs-14:latest`, and sources should be placed in the `/project` directory of the container. Two endpoints are also defined, one to access the micro-service, the other to help the debugger attach to the process, during debugging sessions.

The commands `install`, `run`, `debug` and `test` indicate which commands to execute to respectively build, execute, debug and test the application. For example, the `npm install` command will be executed in the container to build the application, then `npm start` will be executed to start the application.

To deploy the micro-service, the component `outerloop-build` indicates how to build the production image (by using `./Dockerfile`, and creating an image whose name is defined by the variable `CONTAINER_IMAGE` defined at the beginning of the devfile). then, two other components `outerloop-deployment` and `outerloop-service` define the Kubernetes resources to deploy to the cluster. Note that the first one defines a Deployment that will help deploy a container using the image built with the previous `outerloop-build` component.

The `starterProjects` section at the end of the devfile indicates a list of starter projects, that can be downloaded to have an example of program deployable with this devfile.

## Devfile registry

We can see through the previous example that a devfile is generic enough, with only a few specific values, like the endpoints and the image names. A devfile written for a specific language and framework can be used by most of the programs written using this language and framework, with minimum personalization.

A devfile registry is available at https://registry.devfile.io, containing devfiles for a large variety of languages and frameworks, and you can deploy your own registry to make accessible your own devfiles.

## Introducing odo v3

You can find the instructions to install odo v3-alpha1 from [this release page](https://github.com/redhat-developer/odo/releases/tag/v3.0.0-alpha1). The binaries are accessible [here](https://developers.redhat.com/content-gateway/rest/mirror/pub/openshift-v4/clients/odo/v3.0.0~alpha1/).

### Initializing a project

The `odo init` command is the first command to use, before to start using `odo` with your project. The goal of this first step is to get a suitable devfile for your project.

`odo init` will search for devfiles into devfile registries. By default, `odo` is configured to access only one devfile registry (the one specified above), and you can modify the devfile registries `odo` is accessing using the command `odo preference registry`.

This `odo init` command offers two modes, either interactive, or manual. The interactive mode will help you discover the appropriate devfile. To use the interactive mode, you just need to enter `odo init` in your command line.

If you execute this command from a directory containing sources, `odo` will try to recognize the language and framework you are using, will search into the devfile registries you have configured the most appropriate devfile, and give you the choice to use this one, or to search for another one. 

```
$ odo init
  __
 /  \__     Initializing new component
 \__/  \    Files: Source code detected, a Devfile will be determined based upon source code autodetection
 /  \__/    odo version: v3.0.0-alpha1
 \__/

Interactive mode enabled, please answer the following questions:
Based on the files in the current directory odo detected
Language: javascript
Project type: nodejs
The devfile "nodejs" from the registry "DefaultDevfileRegistry" will be downloaded.
? Is this correct? (Y/n) 
```

If you answer `No` here, or if you run the `odo init` command from an empty directory, `odo init` will help you choose the appropriate devfile. The command will also help you make some personalization on the devfile, by personalizing the endpoints and the environment variables for the container that will be deployed during the development phase.

```
? Select language: javascript
? Select project type: Node.js Runtime
 ✓  Downloading devfile "nodejs" from registry "DefaultDevfileRegistry" [961ms]
Current component configuration:
Container "runtime":
  Opened ports:
   - 3000
  Environment variables:
? Select container for which you want to change configuration? NONE - configuration is correct
? Enter component name: my-nodejs-app

Your new component 'my-nodejs-app' is ready in the current directory.
To start editing your component, use 'odo dev' and open this folder in your favorite IDE.
Changes will be directly reflected on the cluster.
```

Finally, if you start the `odo init` command from an empty directory, it will give you the choice to download one of the starter projects listed in the devfile.

## The development phase

Now that a devfile is present in the current directory, you can run your application in development mode, using the `odo dev` command. This command will create a `Deployment` in the cluster that will help start a container as defined in the devfile. Then, the sources present in the current directory will be synchronized into the container, and the commands to build and run the application will be exectued from inside the container.

At the same time, a port-forwarding will be done for each endpoint defined in the devfile, so you can access the container ports through local ports in your development machine.

Finally, `odo` will watch for changes in the current directory. When files are modified, added or deleted, `odo` will synchronize the changes to the container, and will restart the build and run commands from inside the container.

```
$ odo dev
  __
 /  \__     Developing using the my-nodejs-app Devfile
 \__/  \    Namespace: prj2
 /  \__/    odo version: v3.0.0-alpha1
 \__/

↪ Deploying to the cluster in developer mode
 ✓  Waiting for Kubernetes resources [6s]
 ✓  Syncing files into the container [439ms]
 ✓  Building your application in container on cluster [3s]
 ✓  Executing the application [1s]

Your application is now running on the cluster
 - Forwarding from 127.0.0.1:40001 -> 3000
 - Forwarding from 127.0.0.1:40002 -> 5858

Watching for changes in the current directory /home/phmartin/Documents/tests/devto-deploy
Press Ctrl+c to exit `odo dev` and delete resources from the cluster
```

To be able to debug the application, you will need to run the `odo dev --debug` command instead.

When you have finished the development session, you just need to hit Ctrl-c to stop the `odo dev` command. The command won't terminate immediately, as it will delete the resources it has deployed on the cluster before to exit.

## The deployment phase

When you are satisfied with your program, you may want to deploy it by building the container image using a `Dockerfile`, instead of using a generic image as during the development phase, and by deploying into the cluster personalized resources, instead of the Deployment used during the development phase.

At the time of writing, no devfile of the default devfile registry contains instructions for the deployment phase. By using the devfile provided as example above, the command `odo deploy` will deploy a personalized Deployment and a Service into the cluster, after building the container image using the `Dockerfile` present in the directory.

```
$ odo deploy
  __
 /  \__     Deploying the application using my-nodejs-app Devfile
 \__/  \    Namespace: prj2
 /  \__/    odo version: v3.0.0-alpha1
 \__/

↪ Building & Pushing Container: quay.io/phmartin/myimage
 •  Building image locally  ...
STEP 1/7: FROM docker.io/library/node:17
STEP 2/7: WORKDIR /usr/src/app
[...]
STEP 7/7: CMD [ "node", "server.js" ]
COMMIT quay.io/phmartin/myimage
 ✓  Building image locally [6s]
 •  Pushing image to container registry  ...
[...]
Writing manifest to image destination
Storing signatures
 ✓  Pushing image to container registry [8s]

↪ Deploying Kubernetes Component: my-node
 ✓  Searching resource in cluster 
 ✓  Creating kind Deployment [50ms]

↪ Deploying Kubernetes Component: svc
 ✓  Searching resource in cluster 
 ✓  Creating kind Service [57ms]

Your Devfile has been successfully deployed
```

At any moment, you can check if a component has been deployed, using the `odo list` command.

```
$ odo list
 ✓  Listing components from namespace 'prj2' [61ms]
 NAME             PROJECT TYPE  RUNNING IN  MANAGED 
 * my-nodejs-app  nodejs        Deploy      odo     
```

When you are done with this application, or if you want to undeploy it to work on development mode again, you can use the `odo delete component` to undeploy the component from the cluster.

```
$ odo delete component
Searching resources to delete, please wait...
This will delete "my-nodejs-app" from the namespace "prj2".
 •  The component contains the following resources that will get deleted:
	- Deployment: my-node
	- Service: svc
? Are you sure you want to delete "my-nodejs-app" and all its resources? Yes
The component "my-nodejs-app" is successfully deleted from namespace "prj2"
```