From 84072a813a6be70711def17e1f82944ca07acdb3 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Thu, 25 Apr 2024 20:03:44 -0700 Subject: [PATCH] Add deploying guide. Resolves #171. --- docs/guide/11-deploying.md | 330 ++++++++++++++++++ docs/guide/{11-pastebin.md => 12-pastebin.md} | 0 .../{12-conclusion.md => 13-conclusion.md} | 0 docs/guide/{13-faq.md => 14-faq.md} | 0 4 files changed, 330 insertions(+) create mode 100644 docs/guide/11-deploying.md rename docs/guide/{11-pastebin.md => 12-pastebin.md} (100%) rename docs/guide/{12-conclusion.md => 13-conclusion.md} (100%) rename docs/guide/{13-faq.md => 14-faq.md} (100%) diff --git a/docs/guide/11-deploying.md b/docs/guide/11-deploying.md new file mode 100644 index 00000000..5afd1898 --- /dev/null +++ b/docs/guide/11-deploying.md @@ -0,0 +1,330 @@ ++++ +summary = "how to deploy a Rocket application to production" ++++ + +# Deploying + +This section describes deploying Rocket applications to production. It provides +a general overview as well as concrete, reusable examples for common deployment +scenarios including [self-managed deployments], [containerization], and +[fully-managed deployments]. + +! note: Rocket does not endorse or prefer any particular tools or services. + + Rocket does not endorse or prefer any specific tools or services mentioned in + this guide. They are mentioned in exposition only. Rocket is agnostic to its + deployment environment. + +[containerization]: #containerization +[self-managed deployments]: #self-managed +[fully-managed deployments]: #fully-managed + +## Overview + +For any deployment, it's important to keep in mind: + + 1. **Configuration** + + Minimally, Rocket will need to be configured to listen on the correct + **port** and **address**, typically port `80` or `8080` and address + `0.0.0.0`. Your deployment environment may have different requirements. + Recall that by default, you can set the address and port via the + environment variables `ROCKET_ADDRESS` and `ROCKET_PORT` as well as through + [many other means](../configuration). + + 2. **Asset Bundling** + + If your application serves assets or leverages templates, you may need to + bundle those assets with your application binary. For example, if you serve + static assets from the `./static` directory and enable templates, you'll + need to ensure that those directories are present and **in the current + working directory** that your application binary starts in. + + ```rust + # #[macro_use] extern crate rocket; + + use rocket::fs::FileServer; + use rocket_dyn_templates::Template; + + #[launch] + fn rocket() -> _ { + rocket::build() + .mount("/", FileServer::from("./static")) + .attach(Template::fairing()) + } + ``` + + For the application above, assuming the `template_dir` configuration + parameter hasn't been changed, you'll need to ensure that the `static` and + `templates` directories are placed in the current working directory that + the application will start in. Otherwise, Rocket will refuse to launch. + + 3. **Load Balancing** + + Rocket does not yet have robust support for [DDoS mitigation], so a + production deployment will require placing Rocket behind a load balancer or + reverse proxy that does. If you are deploying your Rocket application to + managed environments such as Kubernetes, Heroku, or Google Cloud Run, this + will be handled for you automatically. However, if you're deploying to a + self-managed environment such as a VPS, we recommend placing Rocket behind + a mature reverse proxy such as HAProxy or NGINX. + + 4. **Service Management** + + As your application matures, you'll need to deploy updated versions to your + production environment, stopping the existing application and starting the + new one in its place. In a managed environment, you can likely rely on the + environment to provide these mechanisms. In a self-managed environment, + using a service manager like `systemd`, _in addition_ to a reverse proxy, + is recommended. + + In either case, it's important to know that once a Rocket application has + started, it will run until [graceful shutdown] is initiated. Your + application should leverage Rocket's graceful shutdown mechanisms such as + the [`Shutdown`] future and [shutdown fairings] to clean-up resources + before terminating. You should also ensure that the graceful shutdown + configuration is aligned with your environment. For example, Kubernetes + issues a `SIGTERM` signal to initiate termination which Rocket listens for + by default, but other environments may send other signals which you might + need to enable as [triggers]. + +The following section addresses these concerns and more for common deployment +scenarios. + +[DDoS mitigation]: @github/issues/1405 +[graceful shutdown]: @api/master/rocket/shutdown/struct.ShutdownConfig.html +[`Shutdown`]: @api/master/rocket/struct.Shutdown.html +[shutdown fairings]: @api/master/rocket/fairing/trait.Fairing.html#shutdown +[triggers]: @api/master/rocket/shutdown/struct.ShutdownConfig.html#triggers + +## Common Scenarios + +### Self-Managed + +In a _self-managed_ environment, you are typically responsible for all facets of +deployment and service management. In exchange, a self-managed environment +typically incurs the lowest financial cost. + +You must decide whether you manage your Rocket application _directly_, by +installing, configuring, and running a service manager, load balancer, and +Rocket application, or _indirectly_ by installing and configuring an application +management service like [kubernetes], [k3s], or [dokku]. Because indirect +self-management typically revolves around [containerization], covered in the +next section, we focus on _direct_ self-management here. + +[kubernetes]: https://kubernetes.io/ +[k3s]: https://k3s.io/ +[dokku]: https://dokku.com/ + +Our recommendation for a direct self-managed deployment is to: + + - **Compile for the remote target (i.e, the VPS), bundle, and copy.** + + Compile your application for the remote target and bundle the binary with + its assets. You may need to cross-compile for the remote target: we + recommend using [`cargo-zigbuild`]. Before cross-compiling, you'll also need + to install the Rust toolchain for the target. + + The script below performs these steps, producing a gzipped archive ready to + be copied to a remote server. + + ```sh + ## configure these for your environment + PKG="app" # cargo package name + TARGET="x86_64-unknown-linux-gnu" # remote target + ASSETS=("Rocket.toml" "static" "templates") # list of assets to bundle + BUILD_DIR="target/${TARGET}/release" # cargo build directory + + ## ensure target toolchain is present + rustup target add $TARGET + + ## cross-compile + cargo zigbuild --target $TARGET --release + + ## bundle + tar -cvzf "${PKG}.tar.gz" "${ASSETS[@]}" -C "${BUILD_DIR}" "${PKG}" + ``` + + - **Run the application as a managed service.** + + Once the bundle is at the remote server, use a service manager to start, + monitor, and stop the application. As an example, assuming the bundle + produced by the script above was extracted to `/www/pkg`, the following + `systemd` service file defines a service for the application: + + ```ini + [Unit] + Description=Rocket Application + After=network.target + + [Service] + Type=simple + WorkingDirectory=/www/pkg + ExecStart=/www/pkg/pkg + User=pkg + Group=pkg + Restart=always + + [Install] + WantedBy=multi-user.target + ``` + + You'll want to modify the service file as needed. Consider particularly the + `User` and `Group`: that user/group will need to be authorized to access the + `WorkingDirectory`. + + Write the service file to the `systemd` services directory (for example, + `/etc/systemd/system/pkg.service`). You can now interact with the service as + usual: + + ```sh + systemctl [status,enable,start,stop,restart,disable] pkg + ``` + + If the service is running but the server doesn't appear to be responding, + ensure that you've set the address and port you expect in the `[default]`, + `[global]`, and/or `[production]` sections of `Rocket.toml` or via another + configuration source. For example, you may wish to set systemd `Service` + environment variables: + + ```diff + [Service] + + Environment=ROCKET_ADDRESS=127.0.0.1 + + Environment=ROCKET_PORT=8000 + ``` + + - **Configure a reverse proxy for the application.** + + Finally, configure a reverse proxy to serve requests to the running + application server. As an example, a simple NGINX reverse proxy + configuration file for the application above might look like: + + ```conf + server { + listen 80; + + location / { + proxy_pass http://127.0.0.1:8000; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + } + } + ``` + + Note that we configure NGINX to forward the actual remote IP via the + `X-Real-IP` header, which Rocket uses by default via [`ip_header`]. + Additionally, the scheme is forwarded via `X-Forwarded-Proto`, but it must + be explicitly configured via [`proxy_proto_header`] for Rocket to consider. + +[`cargo-zigbuild`]: https://github.com/rust-cross/cargo-zigbuild +[`ip_header`]: @api/master/rocket/config/struct.Config.html#structfield.ip_header +[`proxy_proto_header`]: @api/master/rocket/config/struct.Config.html#structfield.proxy_proto_header + +### Containerization + +In a _containerization_ environment, you are responsible for writing a +`Dockerfile` or `Containerfile` which you provide to an application platform. +The platform may be self-managed, as with [k3s] or [dokku], or fully-managed, as +with Google Cloud Run or Heroku. + +Below you'll find an example of a `Dockerfile` that: + + - Builds the application with the latest stable Rust compiler. + - Uses `--mount=type=cache` to avoid recompiling dependencies. + - Uses a second stage to create a slim (~100MiB), ready-to-deploy image with + only what's needed. + - Bundles all of an application's assets in the container. + +```dockerfile +FROM docker.io/rust:1-slim-bookworm AS build + +## cargo package name: customize here or provide via --build-arg +ARG pkg=rocket-app + +WORKDIR /build + +COPY . . + +RUN --mount=type=cache,target=/build/target \ + --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + set -eux; \ + cargo build --release; \ + objcopy --compress-debug-sections target/release/$pkg ./main + +################################################################################ + +FROM docker.io/debian:bookworm-slim + +WORKDIR /app + +## copy the main binary +COPY --from=build /build/main ./ + +## copy runtime assets which may or may not exist +COPY --from=build /build/Rocket.tom[l] ./static +COPY --from=build /build/stati[c] ./static +COPY --from=build /build/template[s] ./templates + +## ensure the container listens globally on port 8080 +ENV ROCKET_ADDRESS=0.0.0.0 +ENV ROCKET_PORT=8080 + +CMD ./main +``` + +You will need to modify the `pkg` `ARG` or provide it via the command-line: + +```sh +docker build --build-arg pkg=cargo_package_name -t app . +``` + +You may also need to make the following changes: + + * Add/remove/modify `ENV` variables as needed. + * Modify the expected `target/release/$pkg` directory. + * Add more assets to `COPY` to the final image. + +Finally, we recommend the following `.dockerignore` file to avoid copying +unnecessary artifacts: + +```gitignore +target +.cargo +**/*.sh +**/*.tar.gz +``` + +### Fully-Managed + +In a _fully-managed_ environment, you provide a service with your source code +and instructions on how to build and run your application. The `Dockerfile` in +the [containerization] section, coupled with a configuration file that instructs +the service to build it, may be one such example. + +Because the specifics on deploying to a fully-managed environment depend on the +environment, we provide only the following general guidelines: + + * **Ensure the address and port are set as required.** + + Most environments require your application to listen on `0.0.0.0`. Ensure + `ROCKET_ADDRESS=0.0.0.0`. + + Some environments require your application to listen on specific ports. + Remember to set the port as required. For example, if the service requires + your application to listen on a port provided by a `$PORT` environment + variable, set `ROCKET_PORT=$PORT` before starting your application. + + * **Compile or run with `--release`.** + + Ensure that you run `cargo` commands with `--release`. Besides compiling + with optimizations, compiling with `--release` sets the default + [configuration profile] to `release`. + + * **Enable debug logging if the application misbehaves.** + + The default log level in `--release` (the release profile) is `critical`. + This level may omit messages helpful in understanding application + misbehavior. To reenable those messages, set `ROCKET_LOG_LEVEL=debug`. + +[configuration profile]: ../configuration/#profiles diff --git a/docs/guide/11-pastebin.md b/docs/guide/12-pastebin.md similarity index 100% rename from docs/guide/11-pastebin.md rename to docs/guide/12-pastebin.md diff --git a/docs/guide/12-conclusion.md b/docs/guide/13-conclusion.md similarity index 100% rename from docs/guide/12-conclusion.md rename to docs/guide/13-conclusion.md diff --git a/docs/guide/13-faq.md b/docs/guide/14-faq.md similarity index 100% rename from docs/guide/13-faq.md rename to docs/guide/14-faq.md