Update Aug 16: In the kubernetes part of this blog I mentioned I hit a bug in the kubernetes deployment part. The maintainers of Ballerina provided a fix, I updated the post to reflect that fix.
Yesterday, I was attending the “Microservices, APIs and Integration” Meetup in Mountain View. There were two presentations, one great presentation on the need for continuous integrating testing by Dave Stank. The second talk of the night was delivered by Lakmal Warusawithana. He touched on the topic of deploying API management right in your service mesh. It was a good talk, showcasing some of the integrations between WSO2 API management and Istio.
What really blew me away during Lakmal’s talk however was the productivity of the Ballerina language. During his demo, Lakmal built a new service in Ballerina, and during the build phase Dockerfiles were generated, alongside with kubernetes manifests, helm charts and Istio configuration. I was blown away that a programming language could be Docker and Kubernetes aware and build its own ‘container’ files.
I decided to do some more research into Ballerina and build this demo myself. And I thought, why not do this in the open and share my learnings (and in the meanwhile, do some advocacy for Ballerina).
What is Ballerina?
Ballerina is a new programming language, primarily developed by the company WSO2. Ballerina was specifically designed to work well in distributed systems and is designed to make the network a first class construct. The Ballerina overview deck, calls out the following:
Ballerina is a compiled, type safe, Turing-complete, concurrent programming language.
Let’s dissect all of these:
- Compiled: A compiled language that is compiled before runtime into machine code or a binary.
- Type safe: A type-safe language is one where the only operations that one can execute on data are the ones that are condoned by the data’s type (source).
- Turing-complete: Hard to find a simple explanation here. I if somebody says “my new thing is Turing Complete” that means in principle (although often not in practice) it could be used to solve any computation problem. (source) Most programming languages are Turing complete, but markup languages (HTML, CSS) are not. For a very in-depth review, check this out.
- Concurrent: Concurrency means you can have multiple independent executions of a process or task. In Ballerina, concurrency is an inherent part of the concept of a function. Concurrency is related to but distinct from parallelism.
Additionally, Ballerina offers both a text-based programming language as well as a graphical sequence-diagram based editor.
It’s very hard to describe a language I just realized. If you want to learn more about the language, I would recommend checking out the Language specification. But I wanted to try things out and see this black magic of Ballerina and the auto-generated Docker/Kubernetes in action.
Setting up Ballerina and running Hello World
I’ll be running this setup on my Ubuntu based WSL.
The latest package to download is available here. To download and install do the following commands:
wget https://product-dist.ballerina.io/downloads/0.991.0/ballerina-linux-installer-x64-0.991.0.deb
sudo dpkg -i ballerina-linux-installer-x64-0.991.0.deb
Now that the language is installed, we can start with an hello-world example. We’ll start with creating a new directory, and initializing our program.
mkdir hello-world
cd hello-world
ballerina init
This created 2 files: Ballerina.toml
and hello_service.bal
. Let’s have a look at the content of both files, starting with the hello_service.bal
:
// A system module containing protocol access constructs
// Module objects referenced with 'http:' in code
import ballerina/http;
import ballerina/io; # A service is a network-accessible API # Advertised on '/hello', port comes from listener endpoint service hello on new http:Listener(9090) { # A resource is an invokable API method # Accessible at '/hello/sayHello # 'caller' is the client invoking this resource # + caller - Server Connector # + request - Request resource function sayHello(http:Caller caller, http:Request request) { // Create object to carry data back to caller http:Response response = new; // Set a string payload to be sent to the caller response.setTextPayload("Hello Ballerina!"); // Send a response back to caller // -> indicates a synchronous network-bound call error? result = caller -> respond(response); if (result is error) { io:println("Error in responding", result); } } }%
This is relatively easy to understand, we basically create a service called hello
, which contains a function sayHello
, that will return a static message. Let’s have a look at the Ballerina.toml
file:
[project] org-name = "nilfranadmin" version = "0.0.1"
Nothing special here, just some metadata.
Let’s go ahead and build our solution and check it out:
ballerina run hello_service.bal
This will start our service, which we can access on localhost:9090
:
But indeed, we configured a specific hello service to listen on the sayHello function. So let’s browse to localhost:9090/hello/sayHello
:
The quick-tour guide in the Ballerina docs continues from here to use ballerina to interact with Twitter to send a tweet. I might run that example later, but I want to see the Docker/Kubernetes magic first. Let’s have a look here.
Docker / Kubernetes integration
So, let’s take our service, and try to turn it into a container and then into a Kubernetes service running on an existing cluster. If you’re only interested in the Kubernetes magic, you can skip the next section and continue to the Kubernetes section. If you’re looking to understand, I recommend following along completely.
We’ll start with turning our existing hello-world service into a Docker container. There’s 3 changes you’ll need to make to your existing hello_service.bal
to make this work. (btw. I assume you already have docker setup and the docker daemon is running)
import ballerina/http;
import ballerina/io;
//Change 1: import docker module.
import ballerinax/docker;
//Change 2: add @docker:Expose{} and define http:listener here.
//Change listener further on to helloWorldEP
@docker:Expose {}
listener http:Listener helloWorldEP = new(9090);
//Change 3: add Docker configuration. Image name and version.
@docker:Config{
name: "helloworld",
tag: "v0.1"
}
service hello on helloWorldEP {
resource function sayHello(http:Caller caller, http:Request request) {
http:Response response = new;
response.setTextPayload("Hello Ballerina!");
error? result = caller -> respond(response);
if (result is error) {
io:println("Error in responding", result);
} }
}
And with those three changes made, we can go ahead and build our service and our Docker container:
sudo ballerina build hello_service.bal
sudo docker run -d -p 9090:9090 helloworld:v0.1
And if we then again connect to our localhost on port 9090, we see our same ballerina service running in a container!
Let’s have a look at what happened. During the build phase, Ballerina created a couple of files:
tree
. ├── Ballerina.toml ├── ballerina-internal.log ├── hello_service.bal
└── target
├── Ballerina.lock
├── hello_service
│ └── Dockerfile
└── hello_service.balx
2 directories, 6 files
What we see here is that in the target folder, ballerina created our ballerina executable in the hello_service.balx
file, and created a Dockerfile
as well in a subfolder. NEAT
But, our end goal is to run this on a Kubernetes cluster. Which will require us to push our image into a registry. Let’s push our image into an Azure Container Registry. If you don’t have one, you can create one in the Azure portal, or use any other registry. What you’ll need it the following:
- Registry FQDN
- Username
- Password
Then we’ll add that to the @docker:Config
part of our hello_service.bal
file:
@docker:Config{
name: "helloworld",
tag: "v0.1",
push:true,
registry: "nilfran.azurecr.io",
username: "nilfran",
password: "notSharingMyPWwithYou"
}
With that, we can execute another sudo ballerina build hello_service.bal
, and if you’re patient (the upload takes a minute) you’ll see the image pop up in your registry!
The kubernetes magic
Let’s now try the same with Kubernetes. First things first, forget about the Docker piece. That was nice, but you don’t need it at all for the Kubernetes part – the deployment manager in Ballerina will build and push the image for you. Now, I hit (what I believe to be) a bug in the kubernetes deployment manager in Ballerina – but I’ll walk you through getting to a working example! and the maintainers of Ballerina were able to help out with providing a fix – for which I updated the post.
Let’s start with our code. This will look similar to our Docker code, with a few simple changes:
import ballerina/http;
import ballerina/io;
import ballerinax/kubernetes;
@kubernetes:Service {
serviceType: "LoadBalancer"
}
listener http:Listener helloWorldEP = new(9090);
@kubernetes:Deployment {
livenessProbe: true,
image: "nilfran.azurecr.io/hello-kubernetes:v1.0",
buildImage: true,
push: true,
// registry: "nilfran.azurecr.io",
username: "nilfran",
password: "nothingToSeeHere"
}
service hello on helloWorldEP {
resource function sayHello(http:Caller caller, http:Request request) {
http:Response response = new;
response.setTextPayload("Hello Ballerina!");
error? result = caller -> respond(response);
if (result is error) {
io:println("Error in responding", result);
} }
}
As you can see, we removed our Docker block, and we changed that with a Kubernetes block. We can go ahead and build this:
sudo ballerina build hello_service.bal
This will build our image, push it to our registry and it will create the necessary yaml files and helm charts. Now, I mentioned that bug before? That’s why we removed the registry reference in the kubernetes deployment part of the file. Here is where you’ll quickly want to make a change in the yaml file – to remove a redundant registry name.
vi target/kubernetes/hello_service/hello_service.yaml
In that file, you see a line that looks like this:
- image: "nilfran.azurecr.io/nilfran.azurecr.io/hello-kubernetes:v1.0"
Remove the redundant nilfran.azurecr.io/ (or your container repo name).
With that done, you can do a kubectl apply -f target/kubernetes/hello_service/hello_service.yaml
and see everything get deployed. A kubectl get all
should show the following resources:
NAME READY STATUS RESTARTS AGE
pod/hello-service-deployment-6667b7dbb6-hrnpj 1/1 Running 0 27m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/helloworldep-svc LoadBalancer 10.0.117.180 52.191.184.15 9090:32159/TCP 59m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/hello-service-deployment 1/1 1 1 59m
NAME DESIRED CURRENT READY AGE
replicaset.apps/hello-service-deployment-6667b7dbb6 1 1 1 27m
The second entry here is a service, of type LoadBalancer, which is publically accessible. Go over to that IP and access the service and now you have your Ballerina service up and running in Kubernetes.
Summary
I hope that I showed what amazed me yesterday during the Meetup. How easy it is to from within Ballerina create kubernetes YAML and automatically create and push your Docker images to a registry.