Kubernetes with OpenShift World Tour: Get hands-on experience and build applications fast! Find a workshop!

Use Appsody as a companion to building and deploying Rust applications

The Rust programming language is a big hit with the software development community, building on the strength of its versatility, compiled-code performance, and innovative memory management model.

Appsody improves software developers’ productivity through several stages of the application development cycle. Appsody has commands for continuously reassembling and rerunning application code upon code changes, as well as commands for deploying the application to a development cluster without forcing developers to deal with the typical Docker and Kubernetes scaffolding.

In this tutorial, we cover the recently released Appsody experimental stack for Rust, going through a couple of examples from The Rust Programming Language book, passing through a small web server application, and deploying that packaged application to a running Kubernetes cluster.

Prerequisites

Complete the following steps to build and test applications on your local workstation:

  • Install the Appsody CLI.

  • Install Docker. If using Windows or macOS, Docker Desktop is probably the best choice. If using a Unix system, minikube and its internal Docker registry are a good alternative.

  • Install Kubernetes. minikube will work on most platforms, but if using Docker Desktop, the internal Kubernetes enabled from the Docker Desktop Preferences (macOS) or Settings (Windows) panel is a more convenient alternative.

Estimated time

With the prerequisites in place, you should be able to complete this tutorial in less than 30 minutes.

Steps

Following along to this tutorial, you will perform the following steps:

  1. Create the application
  2. Run the application
  3. Run an interactive application
  4. Implement a web server
  5. (Optional) Package a complete web server
  6. Deploy the application to a cluster

At the end of the tutorial, you will have progressed through the following phases:

Tutorial progress

1. Create the application

Appsody is built around the concept of Appsody stacks, combining container stacks with pre-packaged code templates. You will begin by using the Appsody command-line interface (CLI) to create the application using the experimental Rust stack.

Open a terminal window and run the following commands to create the application directory and prime it with the template of a simple Rust application bundled with the experimental Rust stack.

mkdir rust-microservice
cd rust-microservice

appsody init experimental/rust

This step creates all the Rust source code and Cargo.toml files inside the application directory. The core files are listed below:

Cargo.toml
src/main.rs

You can take a moment to modify the [package] section of the Cargo.toml file, but that is not necessary for this tutorial.

You will also notice the src/main.rs source file, containing a small Rust program:

fn main() {
    println!("Hello from Appsody!");
}

2. Run the application

With the application created, it is time to run it for the first time.

While still in the same terminal and directory where you created the application, execute the following command:

appsody run

The first run of Appsody may take a little longer than usual, downloading and caching application dependencies. Appsody will start the application once it completes the downloads of prerequisites and compilation of the source code:

Running development environment...
Pulling docker image appsody/rust:0.1
Running command: docker pull appsody/rust:0.1
0.1: Pulling from appsody/rust
Digest: sha256:f9096a8f7ba742497ffb6e69f3dff3e5d2a01a63e973e917752f2df1abdf3c63
Status: Image is up to date for appsody/rust:0.1
docker.io/appsody/rust:0.1
Running docker command: docker run --rm -p 1234:1234 -p 5000:5000 -p 8000:8000 --name rust-microservice-dev -v /Users/myuser/workspace/rust-microservice/.:/project/user-app -v rust-microservice-deps:/usr/local/cargo/deps -v /Users/myuser/.appsody/appsody-controller:/appsody/appsody-controller -t --entrypoint /appsody/appsody-controller appsody/rust:0.1 --mode=run
[Container] Running command:  cargo run
[Container]    Compiling rust-simple v0.1.0 (/project/user-app)
[Container]     Finished dev [unoptimized + debuginfo] target(s) in 1.82s
[Container]      Running `target/debug/rust-simple`
[Container] Hello from Appsody!

At this point, you will notice you did not have to download and set up Rust development tools, which are bundled with the Appsody Rust stack.

Let’s now make modifications to the small application and observe where Appsody starts to set itself apart as a good companion to the development of Rust applications.

Replace the line println!("Hello from Appsody!"); in src/main.rs with:

    println!("Hello from Appsody (modified)!");

And you will see, Appsody detects the change and triggers another run of cargo run inside the container:

[Container]    Compiling rust-simple v0.1.0 (/project/user-app)
[Container]     Finished dev [unoptimized + debuginfo] target(s) in 0.88s
[Container]      Running `target/debug/rust-simple`
[Container] Hello from Appsody (modified)!

If you are running through code changes in something like the Rust Programming Language book, this setup will be enough to run most of the examples, except for those requiring user input, which leads us to the next step.

For now, stop the application container with the combination of "Ctrl+C" keys, then run:

appsody stop

3. Run an interactive application

If you are going through the examples in “The Rust Programming Language” book, you will eventually come across the interactive examples for random number generators and guessing games. In those cases, you will need Appsody to run in interactive mode, accepting input from the command line.

Appsody can accept input from the command line through a command-line option of its own. Start the application in interactive mode, with:

appsody run --interactive

Now replace the contents of src/main.rs with this example from the book:

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1, 101);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin().read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

You will see a compilation failure in the command line, with the highlighted color-coded clarity appreciated by Rust developers. This is expected at this point since we are missing the rand crate dependency in the Cargo.toml file:

Failed compilation

To fix the problem, insert the rand crate into the [dependencies] section of the Cargo.toml file, as follows:

...
edition = "2018"

[dependencies]
rand = "0.7.2"

[[bin]]
...

Appsody will download the new dependency, recompile the application, and run it again:

[Container] Running command:  cargo run
[Container] [Warning] Wait Received error starting process of type APPSODY_RUN/DEBUG/TEST_ON_CHANGE while running command: cargo run error received was: signal: interrupt
[Container]    Compiling rust-simple v0.1.0 (/project/user-app)
[Container]     Finished dev [unoptimized + debuginfo] target(s) in 1.86s
[Container]      Running `target/debug/rust-simple`
[Container] Guess the number!
[Container] Please input your guess.

Once you complete all the steps (and maybe finish playing a round of the guessing game), you could terminate the container again. For now, let’s leave it up, saving some time and typing ahead of the next step, remembering that Appsody will take care of downloading and recompiling whatever is necessary after code changes.

4. Implement a web server

Rust applications can also be good web servers for RESTful applications, using frameworks such as Rocket, Actix or Tower-Web.

As a small example, you can use code like the listing below to listen on port 8000 and always return a greeting message:

use std::io::Write;
use std::net::{TcpListener, TcpStream};

fn main() {
    let listener = TcpListener::bind("0.0.0.0:8000").unwrap();

    for stream in listener.incoming() {
        let stream = stream.unwrap();

        handle_connection(stream);
    }
}

fn handle_connection(mut stream: TcpStream) {
    let header = b"HTTP/1.1 200 OK\n\n";
    let contents = b"<html><body><p>Hello from Appsody!</p></body></html>";
    stream.write(header).unwrap();
    stream.write(contents).unwrap();
    stream.flush().unwrap();
}

Note the usage of address 0.0.0.0:8000 for the network bind address instead of the typical 127.0.01:8000 address seen in most Rust tutorials. This network bind address is chosen to match the internal network interface used inside the container image running the Rust application.

Replace the content of the main.rs file with that content, save it, and wait for Appsody to trigger and complete a new run of cargo run.

Now open the http://localhost:8000 URL in your browser and you should see the message Hello from Appsody!.

5. (Optional) Package a complete web server

Before proceeding to the next step, where you will package and deploy the application to a Kubernetes cluster, you may want to use a realistic web server, which can be useful if you plan to continue experimenting with Rust after the completion of this tutorial.

This section uses the Actix web framework. Other frameworks, such as Tower Web, will work with the Rust experimental stack as well, except for Rocket, which has a dedicated Appsody stack due to the requirement for Rust nightly.

Keep in mind this step will add a few minutes of compilation to the entire tutorial, so you may want to skip it if you are pressed for time.

As the first step, include the Actix crate in the [dependencies] section of the Cargo.toml file:

...

[dependencies]
actix-web = "1.0.8"
...

The next step is to replace the contents of the src/main.rs file with the following snippet, pulled directly from the Actix homepage, modifying the network bind address from 127.0.0.1:8000 to 0.0.0.0:8000 to match the internal network interface settings of the container:

use actix_web::{web, App, HttpRequest, HttpServer, Responder};

fn greet(req: HttpRequest) -> impl Responder {
    let name = req.match_info().get("name").unwrap_or("from Appsody");
    format!("Hello {}!", &name)
}

fn main() {
    HttpServer::new(|| {
        App::new()
            .route("/", web::get().to(greet))
            .route("/{name}", web::get().to(greet))
    })
    .bind("0.0.0.0:8000")
    .expect("Can not bind to port 8000")
    .run()
    .unwrap();
}

This first run will take a few minutes, with Appsody triggering a call of cargo run, which will download and compile all dependencies for the new crate. Once the compilation phase completes, you should see the following lines at the end of the command-line output:

...
[Container]    Compiling awc v0.2.8
[Container]    Compiling actix-web v1.0.8
[Container]    Compiling rust-appsody v0.1.0 (/project/user-app)
[Container]     Finished dev [unoptimized + debuginfo] target(s) in 2m 22s
[Container]      Running `target/debug/rust-simple`

Launch the /{name} endpoint of the application from your web browser, at http://localhost:8000/Appsody

6. Deploy the application to a cluster

With our example web server ready, it is time to see another Appsody strength in action: packaging and deploying the entire application to a Kubernetes cluster without requiring interactions with Dockerfile or deployment YAML files.

You may still have an application running in a container from the previous steps, so stop it with the Ctrl+C keys and type appsody stop in the command line.

The next steps assume you have your local Kubernetes instance running (using the Kubernetes cluster enabled in Docker Desktop or minikube) and that your Docker command line is logged into the Docker registry for that cluster.

The Appsody deployment command builds the container first if it has not been built before, but let’s take a look at the build command first. Type the following command in the terminal:

appsody build

The operation will take a little longer than appsody run since the build process may require updates to the local cache of images, as well as the generation of a new image.

The build command also has flags for handing out parameters to the Docker build as well as tagging the final image with a different name than the default image name (the application directory name is used as the default image name).

With the build complete, it is time to deploy the application to the cluster. Type the following command:

appsody deploy

This request will trigger the creation of the container image for the application and subsequent deployment to the cluster. Once the container build is complete, you should see output similar to the following:

Running command: kubectl apply -f app-deploy.yaml --namespace default
Deployment succeeded.
Appsody Deployment name is: rust-microservice
Running command: kubectl get rt rust-microservice -o jsonpath="{.status.url}" --namespace default
Attempting to get resource from Kubernetes ...
Running command: kubectl get route rust-microservice -o jsonpath={.status.ingress[0].host} --namespace default
Attempting to get resource from Kubernetes ...
Running command: kubectl get svc rust-microservice -o jsonpath=http://{.status.loadBalancer.ingress[0].hostname}:{.spec.ports[0].nodePort} --namespace default
Deployed project running at http://localhost:30728

The last line should have the final URL for the application in the cluster. If you are using minikube, and depending on how your local network is set up, you may need to issue this command to get the URL for the application:

minikube service rust-microservice --url

Open that URL in a web browser window and you will see the Hello from Appsody! message again.

Unwinding the whole environment

Once you are done with all the tests, it is time to delete the cluster deployment and the containers created in this tutorial.

Remove the deployment from the Kubernetes cluster with:

appsody deploy delete

Summary

After completing this tutorial, you should feel comfortable using Appsody as a companion in the development of Rust applications, from continuous local build cycles, all the way to deployment to a Kubernetes cluster.

Next steps

  • Extend your application with Codewind: To understand the larger developer experience for Appsody, read this tutorial which covers Codewind extensions to popular IDEs. You can use Codewind to import the application you created in this tutorial into your IDE and iterate more fluidly through code changes.
  • Create new Appsody stacks or templates: If you are ready to create a stack of your own or maybe add a new application template to the Rust collection in Appsody, read the article about Customizing Appsody which explains the concept of Appsody collections and then dive into this tutorial which shows you how to create your own Appsody stack.
  • Discover Kabanero: As a final suggestion, check out the Kabanero.io project, which brings together Appsody, Codewind, and other open source projects, to deliver an integrated DevOps experience from application developers to application deployment and operations.

Acknowledgments

Thanks to Hans Uhlig for reviewing large portions of this tutorial, and especially in advising on the techniques used in the samples.

Denilson Nastacio