diff --git a/README.Docker.md b/README.Docker.md new file mode 100644 index 00000000000..3deb01c4a56 --- /dev/null +++ b/README.Docker.md @@ -0,0 +1,22 @@ +### Building and running your application + +When you're ready, start your application by running: +`docker compose up --build`. + +Your application will be available at http://localhost:8233. + +### Deploying your application to the cloud + +First, build your image, e.g.: `docker build -t myapp .`. +If your cloud uses a different CPU architecture than your development +machine (e.g., you are on a Mac M1 and your cloud provider is amd64), +you'll want to build the image for that platform, e.g.: +`docker build --platform=linux/amd64 -t myapp .`. + +Then, push it to your registry, e.g. `docker push myregistry.com/myapp`. + +Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/) +docs for more detail on building and pushing. + +### References +* [Docker's Rust guide](https://docs.docker.com/language/rust/) \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 1ccaa5915a5..a05d468a997 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,9 +1,10 @@ +# syntax=docker/dockerfile:1 +# check=skip=UndefinedVar + # If you want to include a file in the Docker image, add it to .dockerignore. # -# We are using five stages: -# - chef: installs cargo-chef -# - planner: computes the recipe file -# - deps: caches our dependencies and sets the needed variables +# We are using 4 stages: +# - deps: install build dependencies and sets the needed variables # - tests: builds tests # - release: builds release binary # - runtime: is our runtime environment @@ -20,29 +21,17 @@ ARG TEST_FEATURES="lightwalletd-grpc-tests zebra-checkpoints" ARG EXPERIMENTAL_FEATURES="" ARG APP_HOME="/opt/zebrad" -# This stage implements cargo-chef for docker layer caching -FROM rust:bookworm as chef -RUN cargo install cargo-chef --locked - -ARG APP_HOME -ENV APP_HOME=${APP_HOME} -WORKDIR ${APP_HOME} - -# Analyze the current project to determine the minimum subset of files -# (Cargo.lock and Cargo.toml manifests) required to build it and cache dependencies -# -# The recipe.json is the equivalent of the Python requirements.txt file -FROM chef AS planner -COPY . . -RUN cargo chef prepare --recipe-path recipe.json - +ARG RUST_VERSION=1.79.0 # In this stage we download all system requirements to build the project # # It also captures all the build arguments to be used as environment variables. # We set defaults for the arguments, in case the build does not include this information. -FROM chef AS deps +FROM rust:${RUST_VERSION}-bookworm AS deps SHELL ["/bin/bash", "-xo", "pipefail", "-c"] -COPY --from=planner ${APP_HOME}/recipe.json recipe.json + +ARG APP_HOME +ENV APP_HOME=${APP_HOME} +WORKDIR ${APP_HOME} # Install zebra build deps and Dockerfile deps RUN apt-get -qq update && \ @@ -52,27 +41,8 @@ RUN apt-get -qq update && \ clang \ ca-certificates \ protobuf-compiler \ - rsync \ rocksdb-tools \ - ; \ - rm -rf /var/lib/apt/lists/* /tmp/* - -# Install google OS Config agent to be able to get information from the VMs being deployed -# into GCP for integration testing purposes, and as Mainnet nodes -# TODO: this shouldn't be a hardcoded requirement for everyone -RUN if [ "$(uname -m)" != "aarch64" ]; then \ - apt-get -qq update && \ - apt-get -qq install -y --no-install-recommends \ - curl \ - lsb-release \ - && \ - echo "deb http://packages.cloud.google.com/apt google-compute-engine-$(lsb_release -cs)-stable main" > /etc/apt/sources.list.d/google-compute-engine.list && \ - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \ - apt-get -qq update && \ - apt-get -qq install -y --no-install-recommends google-osconfig-agent; \ - fi \ - && \ - rm -rf /var/lib/apt/lists/* /tmp/* + && rm -rf /var/lib/apt/lists/* /tmp/* # Build arguments and variables set for tracelog levels and debug information # @@ -90,7 +60,7 @@ ARG COLORBT_SHOW_HIDDEN ENV COLORBT_SHOW_HIDDEN=${COLORBT_SHOW_HIDDEN:-1} ARG SHORT_SHA -# If this is not set, it must be the empty string, so Zebra can try an alternative git commit source: +# If this is not set, it must be an empty string, so Zebra can try an alternative git commit source: # https://github.com/ZcashFoundation/zebra/blob/9ebd56092bcdfc1a09062e15a0574c94af37f389/zebrad/src/application.rs#L179-L182 ENV SHORT_SHA=${SHORT_SHA:-} @@ -102,12 +72,6 @@ ENV CARGO_HOME="${APP_HOME}/.cargo/" # An entrypoint.sh is only available in this step for easier test handling with variables. FROM deps AS tests -COPY --from=electriccoinco/lightwalletd:latest /usr/local/bin/lightwalletd /usr/local/bin/ - -# cargo uses timestamps for its cache, so they need to be in this order: -# unmodified source files < previous build cache < modified source files -COPY . . - # Skip IPv6 tests by default, as some CI environment don't have IPv6 available ARG ZEBRA_SKIP_IPV6_TESTS ENV ZEBRA_SKIP_IPV6_TESTS=${ZEBRA_SKIP_IPV6_TESTS:-1} @@ -120,26 +84,41 @@ ARG EXPERIMENTAL_FEATURES # TODO: add empty $EXPERIMENTAL_FEATURES when we can avoid adding an extra space to the end of the string ARG ENTRYPOINT_FEATURES="${FEATURES} ${TEST_FEATURES}" -# Re-hydrate the minimum project skeleton identified by `cargo chef prepare` in the planner stage, -# over the top of the original source files, -# and build it to cache all possible sentry and test dependencies. -# -# This is the caching Docker layer for Rust tests! -# It creates fake empty test binaries so dependencies are built, but Zebra is not fully built. -# -# TODO: add --locked when cargo-chef supports it -RUN cargo chef cook --tests --release --features "${ENTRYPOINT_FEATURES}" --workspace --recipe-path recipe.json -# Undo the source file changes made by cargo-chef. -# rsync invalidates the cargo cache for the changed files only, by updating their timestamps. -# This makes sure the fake empty binaries created by cargo-chef are rebuilt. -COPY --from=planner ${APP_HOME} zebra-original -RUN rsync --recursive --checksum --itemize-changes --verbose zebra-original/ . -RUN rm -r zebra-original - # Build Zebra test binaries, but don't run them -RUN cargo test --locked --release --features "${ENTRYPOINT_FEATURES}" --workspace --no-run -RUN cp ${APP_HOME}/target/release/zebrad /usr/local/bin -RUN cp ${APP_HOME}/target/release/zebra-checkpoints /usr/local/bin + +# Leverage a cache mount to /usr/local/cargo/registry/ +# for downloaded dependencies, a cache mount to /usr/local/cargo/git/db +# for git repository dependencies, and a cache mount to ${APP_HOME}/target/ for +# compiled dependencies which will speed up subsequent builds. +# Leverage a bind mount to each crate directory to avoid having to copy the +# source code into the container. Once built, copy the executable to an +# output directory before the cache mounted ${APP_HOME}/target/ is unmounted. +RUN --mount=type=bind,source=zebrad,target=zebrad \ + --mount=type=bind,source=zebra-chain,target=zebra-chain \ + --mount=type=bind,source=zebra-network,target=zebra-network \ + --mount=type=bind,source=zebra-state,target=zebra-state \ + --mount=type=bind,source=zebra-script,target=zebra-script \ + --mount=type=bind,source=zebra-consensus,target=zebra-consensus \ + --mount=type=bind,source=zebra-rpc,target=zebra-rpc \ + --mount=type=bind,source=zebra-node-services,target=zebra-node-services \ + --mount=type=bind,source=zebra-test,target=zebra-test \ + --mount=type=bind,source=zebra-utils,target=zebra-utils \ + --mount=type=bind,source=zebra-scan,target=zebra-scan \ + --mount=type=bind,source=zebra-grpc,target=zebra-grpc \ + --mount=type=bind,source=tower-batch-control,target=tower-batch-control \ + --mount=type=bind,source=tower-fallback,target=tower-fallback \ + --mount=type=bind,source=Cargo.toml,target=Cargo.toml \ + --mount=type=bind,source=Cargo.lock,target=Cargo.lock \ + --mount=type=cache,target=${APP_HOME}/target/ \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/usr/local/cargo/registry/ \ +cargo test --locked --release --features "${ENTRYPOINT_FEATURES}" --workspace --no-run && \ +cp ${APP_HOME}/target/release/zebrad /usr/local/bin && \ +cp ${APP_HOME}/target/release/zebra-checkpoints /usr/local/bin + +# Copy the lightwalletd binary and source files to be able to run tests +COPY --from=electriccoinco/lightwalletd:latest /usr/local/bin/lightwalletd /usr/local/bin/ +COPY ./ ./ COPY ./docker/entrypoint.sh /etc/zebrad/entrypoint.sh @@ -154,28 +133,34 @@ ENTRYPOINT [ "/etc/zebrad/entrypoint.sh" ] # In this stage we build a release (generate the zebrad binary) # -# This step also adds `cargo chef` as this stage is completely independent from the +# This step also adds `cache mounts` as this stage is completely independent from the # `test` stage. This step is a dependency for the `runtime` stage, which uses the resulting # zebrad binary from this step. FROM deps AS release -COPY . . - ARG FEATURES -# This is the caching layer for Rust zebrad builds. -# It creates a fake empty zebrad binary, see above for details. -# -# TODO: add --locked when cargo-chef supports it -RUN cargo chef cook --release --features "${FEATURES}" --package zebrad --bin zebrad --recipe-path recipe.json - -# Undo the source file changes made by cargo-chef, so the fake empty zebrad binary is rebuilt. -COPY --from=planner ${APP_HOME} zebra-original -RUN rsync --recursive --checksum --itemize-changes --verbose zebra-original/ . -RUN rm -r zebra-original - -# Build zebrad -RUN cargo build --locked --release --features "${FEATURES}" --package zebrad --bin zebrad +RUN --mount=type=bind,source=zebrad,target=zebrad \ + --mount=type=bind,source=zebra-chain,target=zebra-chain \ + --mount=type=bind,source=zebra-network,target=zebra-network \ + --mount=type=bind,source=zebra-state,target=zebra-state \ + --mount=type=bind,source=zebra-script,target=zebra-script \ + --mount=type=bind,source=zebra-consensus,target=zebra-consensus \ + --mount=type=bind,source=zebra-rpc,target=zebra-rpc \ + --mount=type=bind,source=zebra-node-services,target=zebra-node-services \ + --mount=type=bind,source=zebra-test,target=zebra-test \ + --mount=type=bind,source=zebra-utils,target=zebra-utils \ + --mount=type=bind,source=zebra-scan,target=zebra-scan \ + --mount=type=bind,source=zebra-grpc,target=zebra-grpc \ + --mount=type=bind,source=tower-batch-control,target=tower-batch-control \ + --mount=type=bind,source=tower-fallback,target=tower-fallback \ + --mount=type=bind,source=Cargo.toml,target=Cargo.toml \ + --mount=type=bind,source=Cargo.lock,target=Cargo.lock \ + --mount=type=cache,target=${APP_HOME}/target/ \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/usr/local/cargo/registry/ \ +cargo build --locked --release --features "${FEATURES}" --package zebrad --bin zebrad && \ +cp ${APP_HOME}/target/release/zebrad /usr/local/bin COPY ./docker/entrypoint.sh ./ @@ -189,6 +174,8 @@ FROM debian:bookworm-slim AS runtime ARG APP_HOME ENV APP_HOME=${APP_HOME} WORKDIR ${APP_HOME} +COPY --link --from=release /usr/local/bin /usr/local/bin +COPY --link --from=release /entrypoint.sh / RUN apt-get update && \ apt-get install -y --no-install-recommends \ @@ -196,8 +183,7 @@ RUN apt-get update && \ curl \ rocksdb-tools \ gosu \ - && \ - rm -rf /var/lib/apt/lists/* /tmp/* + && rm -rf /var/lib/apt/lists/* /tmp/* # Create a non-privileged user that the app will run under. # Running as root inside the container is running as root in the Docker host @@ -224,6 +210,7 @@ ARG FEATURES ENV FEATURES=${FEATURES} # Path and name of the config file +# This are set here to always this variable set, even if the user does not set it ENV ZEBRA_CONF_DIR=${ZEBRA_CONF_DIR:-/etc/zebrad} ENV ZEBRA_CONF_FILE=${ZEBRA_CONF_FILE:-zebrad.toml} diff --git a/docker/docker-compose.full.yml b/docker/docker-compose.full.yml new file mode 100644 index 00000000000..815ad941ea6 --- /dev/null +++ b/docker/docker-compose.full.yml @@ -0,0 +1,153 @@ +version: "3.8" + +services: + zebra: + image: zfnd/zebra + platform: linux/amd64 + build: + context: ../ + dockerfile: docker/Dockerfile + target: runtime + restart: unless-stopped + deploy: + resources: + reservations: + cpus: "4" + memory: 16G + depends_on: + prometheus: + condition: service_started + grafana: + condition: service_started + env_file: + - .zebra.env + # Change this to the commmand you want to run, respecting the entrypoint.sh + # For example, to run the tests, use the following command: + # command: ["cargo", "test", "--locked", "--release", "--features", "${TEST_FEATURES}", "--package", "zebrad", "--test", "acceptance", "--", "--nocapture", "--include-ignored", "sync_large_checkpoints_"] + #! Uncomment the following line to use a zebrad.toml from the host machine + # NOTE: This will override the zebrad.toml in the image and make some variables irrelevant + # configs: + # - source: zebra_config + # target: /etc/zebrad/zebrad.toml + # uid: '2001' # Rust's container default user uid + # gid: '2001' # Rust's container default group gid + # mode: 0440 + volumes: + - zebrad-cache:/var/cache/zebrad-cache + - lwd-cache:/var/cache/lwd-cache + ports: + # Zebra uses the following inbound and outbound TCP ports + - "8232:8232" # Opens an RPC endpoint (for wallet storing and mining) + - "8233:8233" # Mainnet Network (for peer connections) + - "18233:18233" # Testnet Network + # - "9999:9999" # Metrics + # - "3000:3000" # Tracing + healthcheck: + start_period: 3m + interval: 15s + timeout: 10s + retries: 3 + # test: ["CMD-SHELL", "curl --data-binary '{\"jsonrpc\": \"1.0\", \"id\":\"curltest\", \"method\": \"getblockchaininfo\", \"params\": [] }' -H 'content-type: application/json' http://127.0.0.1:8232/ || exit 1"] + + lightwalletd: + image: electriccoinco/lightwalletd + platform: linux/amd64 + depends_on: + zebra: + condition: service_started + restart: unless-stopped + deploy: + resources: + reservations: + cpus: "4" + memory: 16G + env_file: + - .lightwalletd.env + configs: + - source: lwd_config + target: /etc/lightwalletd/zcash.conf + volumes: + - litewalletd-data:/var/lib/lightwalletd/db + # This setup with --no-tls-very-insecure is only for testing purposes + #! For production environments follow the guidelines here: https://github.com/zcash/lightwalletd#production-usage + command: > + --no-tls-very-insecure + --grpc-bind-addr=0.0.0.0:9067 + --http-bind-addr=0.0.0.0:9068 + --zcash-conf-path=/etc/lightwalletd/zcash.conf + --data-dir=/var/lib/lightwalletd/db + --log-file=/dev/stdout + --log-level=7 + ports: + - "9067:9067" # gRPC + - "9068:9068" # HTTP + + prometheus: + image: prom/prometheus + configs: + - source: prometheus_config + target: /etc/prometheus/prometheus.yml + volumes: + - prometheus-data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.enable-lifecycle' + ports: + - "9090:9090" + healthcheck: + test: wget --no-verbose --tries=1 --spider http://localhost:9090/status || exit 1 + start_period: 30s + interval: 10s + timeout: 15s + retries: 3 + + grafana: + image: grafana/grafana + volumes: + - grafana-data:/var/lib/grafana + - ../grafana/provisioning/:/etc/grafana/provisioning/ + # environment: + # GF_SECURITY_ADMIN_PASSWORD: ${GF_SECURITY_ADMIN_PASSWORD} + depends_on: + prometheus: + condition: service_healthy + env_file: + - ../grafana/config.monitoring + ports: + - "3000:3000" + healthcheck: + test: wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1 + interval: 30s + timeout: 10s + retries: 3 + +configs: + zebra_config: + # Change the following line to point to a zebrad.toml on your host machine + # to allow for easy configuration changes without rebuilding the image + file: ../zebrad/tests/common/configs/v1.0.0-rc.2.toml/ + + lwd_config: + # Change the following line to point to a zcash.conf on your host machine + # to allow for easy configuration changes without rebuilding the image + file: ./zcash-lightwalletd/zcash.conf + + prometheus_config: + file: ../prometheus.yaml + +volumes: + zebrad-cache: + driver: local + + lwd-cache: + driver: local + + litewalletd-data: + driver: local + + prometheus-data: + driver: local + + grafana-data: + driver: local