Working with Compiled Languages
Working with compiled languages introduces a small additional challenge - the source needs to be compiled before it can run on the container. Raftt has various built-in options for supporting flexible work with compiled languages, including hot-reloading and debugging.
In this guide we will cover the general aspects of working with compiled languages, and the ways you can achieve great DevEx using Raftt. For language-specific examples, check out the debugging section. The examples we give here will be for Go, but the ideas are relevant for all compiled languages.
The following sections assume you are already connected to an environment, and are working on a workload in dev mode.
The most basic problem with compiled languages is that they need to be compiled. If your exiting image includes the toolchain, you can use it directly, and this section is less relevant. Otherwise, keep reading.
If the image does not include the toolchain, we have three options for building the artifact externally, somewhere where the toolchain does exist:
- Add a
buildersidecar container and build on it - simplest
- Add a
builderdeployment and building on it - allows a shared build cache
- Building locally - not recommended, can take a long time to transmit the built artifact
Adding a Builder Sidecar Container
Going this route, all we need to do is add another container to the deployment, with an image that has the required toolchain. We also mount the repo to it:
dep = resources.deployments["my-deployment"]
# Define the container and add it to the workload
builder = k8s.Container(
# Mount the source code to the builder container
dep.mount(repo_root(), "/src", container="builder")
raftt rebuild, you should be able to run
raftt sh my-deployment -c builder and build your code! Because the builder is bound to the lifetime of the deployment which uses its artifact, you will have a working build cache, and subsequent builds should be speedy.
Adding a Builder Deployment
Going this route, we will create a brand new deployment which we will use for building artifacts. This currently requires creating a file outside of the
.raftt file, in which we write the deployment YAML:
- image: golang:1.20.2-alpine3.17
Then in the
.raftt file we would have:
builder = builder_resources.deployments["builder"]
# Mount the source code to the builder container
builder.mount(repo_root(), "/src", container="builder")
raftt rebuild builder, you should be able to run
raftt sh builder and build your code! Because the builder is not bound to the lifetime of any one deployment, you can have a shared build cache. If you are using Raftt in connect-mode, you can add:
Which will make the builder automatically be created once you connect to a new environment.
This mode is less recommended, because it can lead to significant delay while the built artifact is being uploaded to the remote cluster. No change needs to be made to the environment for this mode.
In this case, use
raftt cp to copy the file to the destinaton container.
Now that the artifact is built, we need to update it in the main container. We have two main options:
- Have a shared mount between the
builderand the main container - recommended
raftt cpto copy the file over
To create a shared mount between the containers, we'll create a
volume(), and then use mount it to both places:
build_out = volume("build_out")
dep.mount(build_out, "/out", container="builder", nocopy=True)
dep.mount(build_out, "/path/to/artifact/dir", init_on_rebuild=True)
Note the addition of
nocopy to the builder, which means we do not try to initialize the volume with the contents of the image, and the
init_on_rebuild on the main container, which means we always initialize the contents of the volume from the main container on rebuild.
We may want to use
cp to copy the file directly in certain cases. Particularly when:
- The destination is in the root of the container or there are other files in the containing directory which we don't want to clobber with the mount
- We are building locally, in which case no need to make any modifications to the
.rafttfile - we will just use
To do that, first make sure the
raftt CLI is available on the builder container:
Now, we can update the artifact in the destination container by running the build command and then
raftt cp /out/artifact my-deployment:/path/to/artifact/dir/artifact.
File watching hooks work as usual, but we will want them to run on the builder, compile the artifact, copy it to the mount (or directly to the main container), and finally restart the process in the main container.
dep.add_raftt_cli(container="builder") # for the restart, and possibly the cp
cmd=["go", "build", "-o", "/out/artifact", "main.go"]
# If copying directly, you would add another CMD here with:
# ["raftt", "cp", "artifact", "my-deployment:/path/to/artifact/dir/artifact"]
cmd=["raftt", "restart", "-n", "my-deployment"]
-n we added to the
raftt restart call, which makes the restart not interrupt an interactive (such as debugging) process currently running on the container.
Adding a Before-launch Task to the IDE
To make it easy to use the IDE directly, you can add a before-launch task to the build/run configuration which performs the actual compilation. See how it is defined in Jetbrains IDEs and VSSCode:
- JetBrains IDEs
- VS Code
Add a Run on Raftt Workload "Before launch" task:
And set it to what is needed for building the artifact:
First we create the before launch task, by adding this to
"command": "raftt sh my-deployment -c builder -- go build -o /out/artifact main.go",
"detail": "Build my-deployment"
And then reference this in your debug/run configuration in the
More information on setting uo debug and run configurations is available in the debugging section.