|
| 1 | +# Scala Native in NanoVM Unikernel |
| 2 | +Exploring Scala Native + Nix Flakes + Nix Devshell + Direnv + NanoVM Unikernel. |
| 3 | + |
| 4 | +* https://scala-native.org/en/latest |
| 5 | +* https://github.com/numtide/devshell |
| 6 | +* https://nixos-and-flakes.thiscute.world/nixos-with-flakes/introduction-to-flakes |
| 7 | +* https://direnv.net |
| 8 | +* https://github.com/typelevel/typelevel-nix |
| 9 | +* https://nanovms.com |
| 10 | + |
| 11 | +The Nix sets up the development environment with: |
| 12 | +1. `clang`: C/C++ LLVM compiler https://clang.llvm.org |
| 13 | +1. `jdk`: GraalVM Community Edition: https://www.graalvm.org |
| 14 | +1. `metals`: Scala Metals LSP server: https://scalameta.org/metals |
| 15 | +1. `sbt`: Scala Built Tool: https://www.scala-sbt.org/index.html |
| 16 | +1. `scala-cli`: Scala command-line tool: https://scala-cli.virtuslab.org |
| 17 | +1. `scala-fix`: Scala refactoring and linting tool for Scala: https://scalacenter.github.io/scalafix |
| 18 | +1. `ops`: NanoVM Unikernel build tool: https://github.com/nanovms/ops |
| 19 | +1. `qemu`: QEMU hypervisor: https://www.qemu.org |
| 20 | + |
| 21 | +The `build.sbt` points to `clang` binaries provided by the `devshell`. |
| 22 | + |
| 23 | +You can use this repo without `nix` if all of the above provided by your own environment (but will need to update the [`build.sbt`](./build.sbt)). |
| 24 | + |
| 25 | +As for `nix` users, `cd` into the repository directory and run `nix develop` to drop into the development environment.\ |
| 26 | +Or, if you have `direnv` installed, simply `cd` into the repository directory and do `direnv allow`.\ |
| 27 | +Now, whenever you `cd` into the repository directory the development environment will be activated automatically, |
| 28 | +and erased when you `cd` out of the repository directory. |
| 29 | + |
| 30 | +## Usage |
| 31 | + |
| 32 | +Build the binary: `sbt nativeLink`. \ |
| 33 | + |
| 34 | +Now, let's use the `ops` command to run it as a QEMU virtual machine packaged as unikernel. \ |
| 35 | +It binds to the port 80, so we'll need `sudo`: `sudo ops run --port 80 ./target/scala-3.6.3/unikernel-scala-out`. |
| 36 | + |
| 37 | + |
| 38 | +In another terminal window: `curl localhost`. \ |
| 39 | +Output: |
| 40 | +``` |
| 41 | +Hello from Scala Native NanoVM Unikernel! Your request: Request(method=GET, uri=/, httpVersion=HTTP/1.1, headers=Headers(Host: localhost, User-Agent: curl/8.11.0, Accept: */*), entity=Entity.Empty) |
| 42 | +``` |
| 43 | + |
| 44 | +Packaging: `ops build ./target/scala-3.6.3/unikernel-scala-out`. \ |
| 45 | + |
| 46 | +Verify the image created: `ops image list`. \ |
| 47 | +Output: |
| 48 | +``` |
| 49 | +100% |████████████████████████████████████████| [0s:0s] |
| 50 | +100% |████████████████████████████████████████| [0s:0s] |
| 51 | +Bootable image file:/home/igor/.ops/images/unikernel-scala-out.img |
| 52 | +``` |
| 53 | + |
| 54 | +The resulting image then can be deployed to any cloud hypervisor which uses QEMU, e.g. [DigitalOcean](https://digitalocean.com).: \ |
| 55 | +1. "Create Droplet". |
| 56 | +2. "Choose Image" -> "Custom Images". |
| 57 | +3. "Add Image". |
| 58 | +4. Upload your image from `~/.ops/images/unikernel-scala-out.img`. |
| 59 | +5. Wait for uploading the image and verification. |
| 60 | +6. On your image: "More" -> "Start Droplet". |
| 61 | + |
| 62 | +The app is currently deployed as `http://unikernel.igorramazanov.tech`: |
| 63 | +``` |
| 64 | +curl -v http://unikernel.igorramazanov.tech |
| 65 | +
|
| 66 | +* Host unikernel.igorramazanov.tech:80 was resolved. |
| 67 | +* IPv6: (none) |
| 68 | +* IPv4: 138.68.108.40 |
| 69 | +* Trying 138.68.108.40:80... |
| 70 | +* Connected to unikernel.igorramazanov.tech (138.68.108.40) port 80 |
| 71 | +* using HTTP/1.x |
| 72 | +> GET / HTTP/1.1 |
| 73 | +> Host: unikernel.igorramazanov.tech |
| 74 | +> User-Agent: curl/8.11.0 |
| 75 | +> Accept: */* |
| 76 | +> |
| 77 | +* Request completely sent off |
| 78 | +< HTTP/1.1 200 OK |
| 79 | +< Date: Wed, 12 Feb 2025 14:25:39 GMT |
| 80 | +< Connection: keep-alive |
| 81 | +< Content-Type: text/plain; charset=UTF-8 |
| 82 | +< Content-Length: 195 |
| 83 | +< |
| 84 | +* Connection #0 to host unikernel.igorramazanov.tech left intact |
| 85 | +Hello from Scala Native NanoVM Unikernel! Your request: Request(method=GET, uri=/, httpVersion=HTTP/1.1, headers=Headers(Host: unikernel.igorramazanov.tech, User-Agent: curl/8.11.0, Accept: */*)) |
| 86 | +``` |
| 87 | + |
| 88 | + |
| 89 | +## CI |
| 90 | +This repo uses `nix` based GitHub actions for caching the development environment dependencies, |
| 91 | +instead of the traditional approach with [`coursier/setup-action`](https://github.com/coursier/setup-action) and [`coursier/cache-action`](https://github.com/coursier/cache-action): |
| 92 | +1. https://github.com/DeterminateSystems/nix-installer-action |
| 93 | +1. https://github.com/DeterminateSystems/flake-checker-action |
| 94 | +1. https://github.com/DeterminateSystems/magic-nix-cache-action |
| 95 | + |
| 96 | +## TODOs |
| 97 | +- [ ] non-Nix friendly. |
| 98 | +- [ ] build and package with Nix Flakes. |
| 99 | +- [ ] automatically build and publish the ELF and the unikernel image. |
| 100 | +- [ ] continuous deployment to DigitalOcean. |
0 commit comments