This blog will show how odo
can now be used with tools such as Helm, Kustomize, etc. for the outerloop development cycle.
This blog is an extension of an earlier blog which focuses on the innerloop development cycle.
By the end of this blog, we will have deployed a CRUD REST mongodb application on a minikube cluster.
Prerequisites:
1. Fetch the project
git clone https://github.com/valaparthvi/restapi-mongodb-odo.git && cd restapi-mongodb-odo
2. Create namespace
Create a namespace called restapi-mongodb
:
odo create namespace restapi-mongodb
Sample output:
3. Initialize the component
Download the devfile to initialize an odo
component with odo init
.
odo init --devfile go --name places
Sample output:
4. Modify the Devfile
We will be using odo deploy
for deploying our application, and for this we need to modify the Devfile by adding required commands and components.
Add the commands
Let us begin by first adding a deploy
command under the commands
section.
# This is the main "composite" command that will run all below commands
- id: deploy
composite:
commands:
- k8s-serviceaccount-for-helm
- k8s-role-for-helm
- k8s-rolebinding-for-helm
- deploy-db
- build-image
- k8s-deployment
- k8s-service
- k8s-url
group:
isDefault: true
kind: deploy
deploy
command is a composition of various other commands in the order in which we want them to be executed.
For e.g. before deploying the database with helm, we need to ensure a service account with the required permissions (made possible by role and rolebinding) has been created;
and so we run k8s-serviceaccount-for-helm
, k8s-role-for-helm
and k8s-rolebinding-for-helm
before running deploy-db
command.
Let us now add the individual commands.
We will first define the deploy-db
command that is used to deploy the helm chart.
To use an external tool such as helm or kustomize, we need to ensure 2 things:
- use an
exec
command; learn more here. - the container component referenced by this command uses an image that contains the required binary.
- id: deploy-db
exec:
commandLine: helm repo add bitnami https://charts.bitnami.com/bitnami && helm repo update && helm upgrade --install mongodb bitnami/mongodb
component: deploy-db
We will now add the remaining commands.
- id: k8s-serviceaccount-for-helm
apply:
component: outerloop-serviceaccount
- id: k8s-role-for-helm
apply:
component: outerloop-role
- id: k8s-rolebinding-for-helm
apply:
component: outerloop-rolebinding
- id: build-image
apply:
component: outerloop-build
- id: k8s-deployment
apply:
component: outerloop-deployment
- id: k8s-service
apply:
component: outerloop-service
- id: k8s-url
apply:
component: outerloop-url
Add the components
Every command above references a component
, and so we now add components under the components
section.
We will first add the component referenced by deploy-db
command.
- name: deploy-db
container:
image: quay.io/tkral/devbox-demo-devbox
attributes:
pod-overrides:
spec:
serviceAccountName: my-go-app
The image used by this container component contains the Helm binary that we can use to deploy the helm chart.
The component is using a pod-overrides
attribute that will override the service account used by the pod to deploy the helm chart to use the service account (my-go-app
) we define in this Devfile.
If we do not do this, the pod will use the default
service account that does not have the required permissions.
We will now add the remaining components.
- name: outerloop-serviceaccount
kubernetes:
inlined: |
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{RESOURCE_NAME}}
- name: outerloop-role
kubernetes:
inlined: |
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{RESOURCE_NAME}}
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- name: outerloop-rolebinding
kubernetes:
inlined: |
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{RESOURCE_NAME}}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{RESOURCE_NAME}}
subjects:
- kind: ServiceAccount
name: {{RESOURCE_NAME}}
# This will build the container image before deployment
- name: outerloop-build
image:
dockerfile:
buildContext: ${PROJECT_SOURCE}
rootRequired: false
uri: ./Dockerfile
imageName: "{{CONTAINER_IMAGE}}"
# This will create a Deployment in order to run your container image across the cluster.
# Note that we expose the env vars necessary to connect application with the mongodb service.
- name: outerloop-deployment
kubernetes:
inlined: |
kind: Deployment
apiVersion: apps/v1
metadata:
name: {{RESOURCE_NAME}}
spec:
replicas: 1
selector:
matchLabels:
app: {{RESOURCE_NAME}}
template:
metadata:
labels:
app: {{RESOURCE_NAME}}
spec:
containers:
- name: {{RESOURCE_NAME}}
image: {{CONTAINER_IMAGE}}
ports:
- name: http
containerPort: {{CONTAINER_PORT}}
protocol: TCP
env:
- name: username
value: {{USERNAME}}
- name: host
value: {{HOST}}
- name: password
valueFrom:
secretKeyRef:
name: mongodb
key: mongodb-root-password
resources:
limits:
memory: "1024Mi"
cpu: "500m"
# This will create a Service so your Deployment is accessible.
# Depending on your cluster, you may modify this code so it's a
# NodePort, ClusterIP or a LoadBalancer service.
- name: outerloop-service
kubernetes:
inlined: |
apiVersion: v1
kind: Service
metadata:
name: {{RESOURCE_NAME}}
spec:
ports:
- name: "{{CONTAINER_PORT}}"
port: {{CONTAINER_PORT}}
protocol: TCP
targetPort: {{CONTAINER_PORT}}
selector:
app: {{RESOURCE_NAME}}
type: NodePort
- name: outerloop-url
kubernetes:
inlined: |
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{RESOURCE_NAME}}
spec:
rules:
- host: "{{DOMAIN_NAME}}"
http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: {{RESOURCE_NAME}}
port:
number: {{CONTAINER_PORT}}
Add the variables
Next, we add a variables
section to the Devfile, so that we can make use of the same variables at multiple locations within the Devfile.
# Add the following variables code anywhere in devfile.yaml
# This MUST be a container registry you are able to access
variables:
CONTAINER_IMAGE: quay.io/MYUSERNAME/go-odo-example
RESOURCE_NAME: my-go-app
CONTAINER_PORT: "8080"
DOMAIN_NAME: go.example.com
USERNAME: root
HOST: mongodb
Ensure that you replace MYUSERNAME
in CONTAINER_IMAGE
with your own username; or use a container registry that you have the write permissions to.
If you are using quay.io registry, you might have to change the repository permissions to Public to continue, otherwise you might see failures related to pulling the image.
Modify schemaVersion
One last thing is to change the schemaVersion
of the Devfile since deploy
commands are only supported in schema 2.2.0+.
# Deploy "kind" ID's use schema 2.2.0+
schemaVersion: 2.2.0
Your final Devfile will look like the following:
5. Deploy
Now that the Devfile is ready, we can simply run odo deploy
.
odo deploy --var CONTAINER_IMAGE=quay.io/<username>/go-odo-example
Sample output
6. Accessing the application
Run odo describe component
to obtain access information.
odo describe component
Sample output
Since we are using Ingress, we first need to check if an IP address has been set.
$ kubectl get ingress my-go-app
NAME CLASS HOSTS ADDRESS PORTS AGE
my-go-app nginx go.example.com 192.168.59.124 80 7m4s
Once the IP address appears, you can now access the application at the following URL:
curl --resolve "go.example.com:80:192.168.59.124" -i http://go.example.com/api/places
Sample output
This will return a null response since the database is currently empty, but it also means that we have successfully connected to our database application.
You can add the following line to the /etc/hosts
file of your computer to simply access the application at http://go.example.com.
Learn more about using ingress to access an application.
192.168.59.124 go.example.com
Add some data to the database:
curl --resolve "go.example.com:80:192.168.59.124" -i http://go.example.com/api/places -sSL -XPOST -d '{"title": "Agra", "description": "Land of Tajmahal"}'
Sample Output
Fetch the list of places again:
$ curl --resolve "go.example.com:80:192.168.59.124" -i http://go.example.com/api/places
HTTP/1.1 201 Created
Date: Thu, 27 Apr 2023 10:41:09 GMT
Content-Type: application/json
Content-Length: 81
Connection: keep-alive
{"id":"62c2a0659fa147e382a4db31","title":"Agra","description":"Land of Tajmahal"}
List of available API endpoints
- GET
/api/places
- List all places - POST
/api/places
- Add a new place - PUT
/api/places
- Update a place - GET
/api/places/<id>
- Fetch place with id<id>
- DELETE
/api/places/<id>
- Delete place with id<id>