Ballerina Code to Cloud is designed to allow developers to write code without thinking about the deployment platform.
This greatly simplifies the experience of developing and deploying Ballerina code in the cloud. It also enables using cloud-native technologies easily without in-depth knowledge.
As of now, Ballerina Code to Cloud supports generating the deployment artifacts of the platforms below.
- Docker
- Kubernetes
Set up the prerequisites
To complete this tutorial, you need:
- Docker installed and configured in your machine
- Kubectl installed and configured in a Kubernetes cluster
Note: macOS users with Apple Silicon chips need to set an environment variable named
DOCKER_DEFAULT_PLATFORM
tolinux/amd64
, before building the image. This is because the Ballerina Docker image is not supported on Apple Silicon chips yet.
export DOCKER_DEFAULT_PLATFORM=linux/amd64
How Code to Cloud works
Code to cloud builds the containers and required artifacts by deriving the required values from the code. This process happens when the package is being compiled. To override the default values given by the compiler, the Cloud.toml
file needs to be created in the package directory.
Package layout
Ballerina encourages having one microservice per package. To adhere to that rule, Code to Cloud generates only one container per package. These artifacts can be found in the target
directory of the package. A complete representation of the package layout is as follows.
├── Cloud.toml ├── Ballerina.lock ├── Ballerina.toml ├── Config.toml ├── entry.bal └── target ├── bala │ └── module-0.0.1.bala ├── bin │ └── <module>.jar ├── docker │ └── Dockerfile └── kubernetes └── <module>-0.0.1.yaml
Cloud.toml
The configuration file is used to override the default values generated by Code to Cloud.
Note: The
Cloud.toml
file is completely optional and you only have to specify the values you need to override. If the value is not specified for a certain field, the compiler will take the default value. All the supported properties can be found in the Code to Cloud specification.
Ballerina.toml
Contains metadata about the Ballerina package. This file is used to fetch defaults for deployment artifacts generation.
Note: The
Ballerina.toml
file can be used to specify the build option.
[build-options] # cloud = "docker" cloud = "k8s"
Config.toml
Contains the values, which need to be passed into the configurable variables. To learn more about this, see Configure a sample Ballerina service. When you are using the cloud-related features in Ballerina, the Config.toml
file is not being packed with the container image as it can contain sensitive information. You need to explicitly provide this file in the runtime.
entry.bal
Represents any .bal
file that has an entry point. The compiler will be using this file to retrieve service-related information for the deployment artifacts.
target/docker/
Contains the Docker artifacts generated by Code to Cloud. These artifacts will be required to build the Docker image.
target/kubernetes/
Contains the Kubernetes artifacts generated by Code to Cloud. These artifacts will be required to deploy the Ballerina application in Kubernetes.
Docker deployment
Create the Ballerina package
-
Execute the
bal new hello_docker
command to create a new package namedhello_docker
. -
Replace the content of the
./hello_docker/main.bal
file with the content below.Note: The source is completely focused on the business logic.
main.bal
import ballerina/http; listener http:Listener helloEP = new (9090); configurable string name = ?; service /helloWorld on helloEP { resource function get sayHello() returns string { return string `Hello, ${name}!`; } }
-
Add
cloud = "docker"
under the[build-options]
table into theBallerina.toml
file in the package.Ballerina.toml
[build-options] cloud = "docker"
-
Create a file named
Cloud.toml
in the package directory and add the content below.Cloud.toml
[container.image] repository="wso2inc" # Docker hub name name="hello" # container name tag="v0.1.0" [settings] buildImage=true # you can make this false if you don't want to build the image and you just need the Dockerfile
-
Create the
Config.toml
file in the package directory or anywhere else and paste the following content.Config.toml
name = "Docker"
Generate the artifacts
Execute the bal build
command to build the Ballerina package and view the output below. If you haven't added the cloud option to Ballerina.toml
file, you can execute bal build --cloud=docker
to generate the artifacts and build the package by providing the build option inline.
$ bal build Compiling source wso2/hello_docker:0.1.0 Generating executable Generating artifacts... @kubernetes:Docker - complete 2/2 Execute the below command to run the generated docker image: docker run -d -p 9090:9090 wso2inc/hello:v0.1.0 target/bin/hello_docker.jar
Before invoking the container, let’s observe the Dockerfile below that has been generated. This is a Dockerfile created by the compiler extension from your code to run your HTTP service in the Docker environment easily.
target/docker/hello/Dockerfile
# Auto Generated Dockerfile FROM ballerina/jvm-runtime:1.0 LABEL maintainer="dev@ballerina.io" COPY ballerina-http-2.2.0.jar /home/ballerina/jars/ COPY ballerina-io-1.2.0.jar /home/ballerina/jars/ COPY wso2-hello_docker-0.1.0.jar /home/ballerina/jars/ ... RUN addgroup troupe \ && adduser -S -s /bin/bash -g 'ballerina' -G troupe -D ballerina \ && apk add --update --no-cache bash \ && chown -R ballerina:troupe /usr/bin/java \ && rm -rf /var/cache/apk/* WORKDIR /home/ballerina COPY wso2-hello_docker-0.1.0.jar /home/ballerina EXPOSE 9090 USER ballerina CMD java -Xdiag -cp "anjana-http2_test-0.1.0.jar:jars/*" 'anjana/http2_test/0/$_init'
Execute the Docker image
Follow the steps below to execute the Docker image separately.
-
Execute the
docker images
command to verify if the Docker image is generated.$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE wso2inc/hello v0.1.0 60d95f0928b2 About a minute ago 228MB
-
Execute the
docker run -d -v <path/to/config>/Config.toml:/home/ballerina/Config.toml -p 9090:9090 wso2inc/hello:v0.1.0
command to run the generated Docker image.
Info: Make sure to set the correct path of the created
Config.toml
. Using volume mount is one way of providing theConfig.toml
file into the runtime. For other ways of providing configurable values, see the Ballerina specification.
$ docker run -d -v /home/wso2/c2c-guide/hello_docker/Config.toml:/home/ballerina/Config.toml -p 9090:9090 wso2inc/hello:v0.1.0 c04194eb0b4d0d78cbc8ca55e0527d381d8ab4a1a68f8ea5dd3770a0845d5fbb
- Execute the
curl http://localhost:9090/helloWorld/sayHello
command to access the service.
$ curl http://localhost:9090/helloWorld/sayHello Hello, Docker!
Kubernetes deployment
Create the Ballerina package
Below sample describes a Ballerina application that reads a greeting string from a config map and greets the user upon HTTP request. By following the steps below, you can make a Kubernetes deployment that has container resource limits, horizontal pod autoscaling, config maps and liveness, readiness probes with the help of Code to Cloud.
-
Execute the
bal new hello_k8s
command to create a new package namedhello_k8s
and go inside that directory. -
Replace the content of the
main.bal
file with the content below.main.bal
import ballerina/http; listener http:Listener helloEP = new (9090); configurable string greeting = ?; service /helloWorld on helloEP { resource function get sayHello() returns string|error? { return greeting + ", Kubernetes!"; } }
-
Create
probes.bal
with the following contents.probes.bal
import ballerina/http; listener http:Listener probeEP = new (9091); service /probes on probeEP { resource function get healthz() returns boolean { return true; } resource function get readyz() returns boolean { return true; } }
-
Create the
Config.toml
file in the package directory and paste the following content into it.Config.toml
greeting = "Hello"
-
Add
cloud = "k8s"
under the[build-options]
table into theBallerina.toml
file in the package.Ballerina.toml
[build-options] cloud = "k8s"
-
Create a file named
Cloud.toml
in the package directory and add the content below. You can use some properties from the docker sample here sincecloud="k8s"
option builds both Docker image and Kubernetes. You can see a brief description of the properties in the comments.Cloud.toml
[container.image] repository="wso2inc" # Ex - Docker hub repository name. name="hello-k8s" # Container name tag="v0.1.0" [cloud.deployment] min_memory="100Mi" # Minimum memory required for the container. max_memory="256Mi" # Maximum memory a single container can take. min_cpu="500m" # Minimum CPU required for the container. max_cpu="500m" # Maximum CPU a single container can take. [cloud.deployment.autoscaling] min_replicas=2 # Minimum number of container instances ran at a given time max_replicas=5 # Maximum number of replicas of the container can scale up to cpu=60 # Average CPU utilization of pods [[cloud.config.files]] # Mounts the `Config.toml` file as a config map in Kubernetes. file="./Config.toml" [cloud.deployment.probes.liveness] port=9091 path="/probes/healthz" [cloud.deployment.probes.readiness] port=9091 path="/probes/readyz"
Generate the artifacts
Once you build the Ballerina package, the compiler extension will generate the artifacts required for k8s deployment.
Tip: If you are using Minikube, execute the
eval $(minikube docker-env)
command before building the image if you don't want to push the container to the Docker registry.
Tip: If you don't have the cloud="k8s" entry on the
Ballerina.toml
you can executebal build --cloud=k8s
command to provide the build option inline.
$ bal build Compiling source wso2/hello_k8s:0.1.0 Generating executable Generating artifacts... @kubernetes:Service - complete 1/2 @kubernetes:Service - complete 2/2 @kubernetes:ConfigMap - complete 1/1 @kubernetes:Deployment - complete 1/1 @kubernetes:HPA - complete 1/1 @kubernetes:Docker - complete 2/2 Execute the below command to deploy the Kubernetes artifacts: kubectl apply -f /home/wso2/c2c-guide/hello_k8s/target/kubernetes/hello_k8s Execute the below command to access service via NodePort: kubectl expose deployment hello-k8s-deployment --type=NodePort --name=hello-k8s-svc-local target/bin/hello_k8s.jar
Note: Before invoking the Kubernetes service, observe the Kubernetes yamls that have been generated. You should be able to find services for ports that have been exposed,
HorizontalPodAutoscaler
for scaling and Deployment, and Config maps.
target/kubernetes/hello_k8s
--- --- apiVersion: "v1" kind: "Service" metadata: labels: app: "hello_k8s" name: "hello-k8s-svc" spec: ports: - name: "port-1-hello-k8" port: 9090 protocol: "TCP" targetPort: 9090 - name: "port-2-hello-k8" port: 9091 protocol: "TCP" targetPort: 9091 selector: app: "hello_k8s" type: "ClusterIP" --- apiVersion: "v1" kind: "ConfigMap" metadata: name: "config-config-map" data: Config.toml: "greeting = \"Hello\"\n" --- apiVersion: "apps/v1" kind: "Deployment" metadata: labels: app: "hello_k8s" name: "hello-k8s-deployment" spec: replicas: 1 selector: matchLabels: app: "hello_k8s" template: metadata: labels: app: "hello_k8s" spec: containers: - env: - name: "BAL_CONFIG_FILES" value: "/home/ballerina/conf/Config.toml:" image: "wso2inc/hello-k8s:v0.1.0" lifecycle: preStop: exec: command: - "sleep" - "15" livenessProbe: httpGet: path: "/probes/healthz" port: 9091 initialDelaySeconds: 30 name: "hello-k8s-deployment" ports: - containerPort: 9090 name: "port-1-hello-k8" protocol: "TCP" - containerPort: 9091 name: "port-2-hello-k8" protocol: "TCP" readinessProbe: httpGet: path: "/probes/readyz" port: 9091 initialDelaySeconds: 30 resources: limits: memory: "256Mi" cpu: "500m" requests: memory: "100Mi" cpu: "500m" volumeMounts: - mountPath: "/home/ballerina/conf/" name: "config-config-map-volume" readOnly: false nodeSelector: {} volumes: - configMap: name: "config-config-map" name: "config-config-map-volume" --- apiVersion: "autoscaling/v1" kind: "HorizontalPodAutoscaler" metadata: labels: app: "hello_k8s" name: "hello-k8s-hpa" spec: maxReplicas: 5 minReplicas: 2 scaleTargetRef: apiVersion: "apps/v1" kind: "Deployment" name: "hello-k8s-deployment" targetCPUUtilizationPercentage: 60
Execute the Kubernetes service
Follow the steps below to execute the Kubernetes service.
-
Execute
docker push <repository>/<name>:<tag>
to push the container to docker hub. You can skip this step if you executedeval $(minikube docker-env)
before building the container.$ docker push wso2inc/hello-k8s:v0.1.0
-
Execute the
kubectl apply -f target/kubernetes/hello_k8s
command to execute the service.$ kubectl apply -f target/kubernetes/hello_k8s service/hello-k8s-svc created configmap/hello-k8s-config-json created deployment.apps/hello-k8s-deployment created horizontalpodautoscaler.autoscaling/hello-k8s-hpa created
-
Execute the
kubectl get pods
command to verify the Kubernetes pods.$ kubectl get pods NAME READY STATUS RESTARTS AGE hello-k8s-deployment-577d8dbf8-p8zg5 1/1 Running 0 37s hello-k8s-deployment-577d8dbf8-p8zg5 1/1 Running 0 57s
-
Execute the
kubectl expose deployment hello-k8s-deployment --type=NodePort --name=hello-k8s-svc-local
command to expose the service via NodePort to test in the development environment.$ kubectl expose deployment hello-k8s-deployment --type=NodePort --name=hello-k8s-svc-local service/hello-k8s-svc-local exposed
-
Execute the
kubectl get svc
command to get the EXTERNAL-IP and port of the Kubernetes service.$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-k8s-svc ClusterIP 10.96.173.207 <none> 9090/TCP,9091/TCP 5m11s hello-k8s-svc-local NodePort 10.99.245.41 <none> 9090:30342/TCP,9091:30515/TCP 66s kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 130m
Tip: If you are using Minikube, execute the
minikube ip
command to get the IP address.$ minikube ip 192.168.49.2
-
Execute the
curl http://192.168.49.2:30342/helloWorld/sayHello
command to access the deployed service via cURL.$ curl http://192.168.49.2:30342/helloWorld/sayHello Hello, Kubernetes!
Note: You can visit the Code to Cloud specification for detailed information about all the supported features. As mentioned in the beginning, Code to Cloud is a tool created to make the development process easier. It does not cover the operational properties in Kubernetes. If you want to have these additional features in your deployment, or you need to change the generated Kubernetes artifacts, you can use Kustomize for modifying the generated YAML.