├── .cargo └── config.toml ├── .dockerignore ├── .github ├── actions │ ├── build │ │ └── action.yml │ ├── run-hey-load-test │ │ └── action.yml │ ├── setup-env │ │ └── action.yml │ └── sign │ │ └── action.yml ├── dependabot.yml ├── labeler.yml └── workflows │ ├── action-build.yml │ ├── action-check.yml │ ├── action-test-image.yml │ ├── action-test-k3s.yml │ ├── action-test-kind.yml │ ├── action-test-smoke.yml │ ├── benchmarks.yml │ ├── ci.yml │ ├── docs.yml │ ├── labeler.yml │ ├── release-wasi-demo-app.yml │ ├── release.yml │ ├── sbom.yml │ ├── scorecard-analysis.yml │ └── sign.yml ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── Cross.toml ├── LICENSE ├── MAINTAINERS ├── Makefile ├── NOTICE ├── README.md ├── RELEASE.md ├── SECURITY.md ├── art └── logo │ ├── runwasi_icon1.svg │ ├── runwasi_icon2.svg │ ├── runwasi_icon3.svg │ ├── runwasi_icon4.svg │ ├── runwasi_logo_horizontal.svg │ └── runwasi_logo_icon.svg ├── benches └── containerd-shim-benchmarks │ ├── Cargo.toml │ ├── README.md │ └── benches │ └── wasi-demo-app-benchmarks.rs ├── crates ├── containerd-shim-wamr │ ├── Cargo.toml │ └── src │ │ ├── instance.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ └── tests.rs ├── containerd-shim-wasm-test-modules │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ ├── lib.rs │ │ └── modules │ │ ├── component-hello-world.wasm │ │ ├── custom_entrypoint.wat │ │ ├── exit_code.wat │ │ ├── fetch_rs.wasm │ │ ├── has_default_devices.rs │ │ ├── hello_wasi_http.wasm │ │ ├── hello_wasi_http_csharp.wasm │ │ ├── hello_world.wat │ │ ├── seccomp.rs │ │ ├── simple_component.wat │ │ └── unreachable.wat ├── containerd-shim-wasm │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── containerd │ │ ├── client.rs │ │ ├── lease.rs │ │ └── mod.rs │ │ ├── lib.rs │ │ ├── sandbox │ │ ├── context.rs │ │ ├── mod.rs │ │ └── path.rs │ │ ├── shim │ │ ├── cli.rs │ │ ├── mod.rs │ │ ├── shim.rs │ │ ├── tests.rs │ │ └── wasm.rs │ │ ├── sys │ │ ├── unix │ │ │ ├── container │ │ │ │ ├── container.rs │ │ │ │ ├── executor.rs │ │ │ │ ├── instance.rs │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ └── pid_fd.rs │ │ └── windows │ │ │ ├── container │ │ │ ├── instance.rs │ │ │ └── mod.rs │ │ │ └── mod.rs │ │ ├── test │ │ ├── mod.rs │ │ └── signals.rs │ │ └── testing.rs ├── containerd-shim-wasmedge │ ├── Cargo.toml │ └── src │ │ ├── instance.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ └── tests.rs ├── containerd-shim-wasmer │ ├── Cargo.toml │ └── src │ │ ├── instance.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ └── tests.rs ├── containerd-shim-wasmtime │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── http_proxy.rs │ │ ├── instance.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ └── tests.rs ├── containerd-shimkit │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ ├── sandbox │ │ ├── async_utils.rs │ │ ├── cli.rs │ │ ├── error.rs │ │ ├── instance.rs │ │ ├── instance_utils.rs │ │ ├── mod.rs │ │ ├── oci.rs │ │ ├── shim │ │ │ ├── events.rs │ │ │ ├── instance_data.rs │ │ │ ├── local.rs │ │ │ ├── local │ │ │ │ └── tests.rs │ │ │ ├── mod.rs │ │ │ ├── otel.rs │ │ │ ├── shim.rs │ │ │ └── task_state.rs │ │ └── sync.rs │ │ ├── sys │ │ ├── unix │ │ │ ├── metrics.rs │ │ │ ├── mod.rs │ │ │ └── stdio.rs │ │ └── windows │ │ │ ├── metrics.rs │ │ │ ├── mod.rs │ │ │ └── stdio.rs │ │ └── vendor │ │ ├── README.md │ │ ├── containerd_shim │ │ ├── logger.rs │ │ ├── mod.rs │ │ └── sys │ │ │ ├── mod.rs │ │ │ └── windows │ │ │ ├── mod.rs │ │ │ └── named_pipe_logger.rs │ │ └── mod.rs ├── oci-tar-builder │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── bin.rs │ │ └── lib.rs ├── stress-test │ ├── Cargo.toml │ ├── README.md │ ├── protos │ │ └── github.com │ │ │ └── containerd │ │ │ └── containerd │ │ │ ├── api │ │ │ ├── runtime │ │ │ │ └── task │ │ │ │ │ ├── v2 │ │ │ │ │ └── shim.proto │ │ │ │ │ └── v3 │ │ │ │ │ └── shim.proto │ │ │ ├── services │ │ │ │ └── ttrpc │ │ │ │ │ └── events │ │ │ │ │ └── v1 │ │ │ │ │ └── events.proto │ │ │ └── types │ │ │ │ ├── mount.proto │ │ │ │ └── task │ │ │ │ └── task.proto │ │ │ └── protobuf │ │ │ └── plugin │ │ │ └── fieldpath.proto │ └── src │ │ ├── containerd │ │ ├── client.rs │ │ ├── containerd.rs │ │ ├── mod.rs │ │ ├── shim.rs │ │ └── task.rs │ │ ├── main.rs │ │ ├── mocks │ │ ├── containerd.rs │ │ ├── mod.rs │ │ ├── shim.rs │ │ ├── task.rs │ │ └── task_client.rs │ │ ├── protos.rs │ │ ├── traits.rs │ │ └── utils.rs └── wasi-demo-app │ ├── .cargo │ └── config.toml │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src │ └── main.rs ├── cross ├── Dockerfile.gnu └── Dockerfile.musl ├── docs ├── .gitignore ├── book.toml ├── mermaid-init.js ├── mermaid.min.js └── src │ ├── CONTRIBUTING.md │ ├── README.md │ ├── RELEASE.md │ ├── SUMMARY.md │ ├── art │ └── logo │ │ ├── runwasi_icon1.svg │ │ └── runwasi_icon3.svg │ ├── assets │ ├── benchmark-website.png │ ├── runwasi-architecture.png │ ├── wasmtime-shim-jeager.png │ └── wasmtime-shim-tracing-main.png │ ├── benchmarks.md │ ├── developer │ ├── architecture.md │ ├── docs.md │ └── roadmap.md │ ├── getting-started │ ├── demos.md │ ├── installation.md │ └── quickstart.md │ ├── oci-decision-flow.md │ ├── opentelemetry.md │ ├── resources │ ├── community.md │ ├── faq.md │ └── troubleshooting.md │ └── windows-getting-started.md ├── rust-toolchain.toml ├── rustfmt.toml ├── scripts ├── benchmark-mem.sh ├── bins.sh ├── cleanup-tests.sh ├── crates.jq ├── extract-changelog.sh ├── parse-hey.py ├── setup-cross.sh ├── setup-jeager-and-otel.sh ├── setup-linux.sh ├── setup-windows.sh ├── test-runner.sh ├── verify-jaeger-traces.sh └── version.sh ├── test ├── k3s │ └── bootstrap.sh └── k8s │ ├── Dockerfile │ ├── deploy.oci.yaml │ └── deploy.yaml └── typos.toml /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(windows)'] 2 | rustflags = [ 3 | "-Adead_code", 4 | "-Awarnings", 5 | ] 6 | 7 | [target.'cfg(unix)'] 8 | runner = 'scripts/test-runner.sh' 9 | 10 | [profile.release] 11 | strip = "symbols" 12 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | test/k8s/Dockerfile 3 | test/k8s/_out/* 4 | .github 5 | out 6 | *.md 7 | LICENSE 8 | .gitignore 9 | .dockerignore 10 | docs 11 | bin/ 12 | Dockerfile 13 | docker-bake.hcl 14 | release/ 15 | -------------------------------------------------------------------------------- /.github/actions/build/action.yml: -------------------------------------------------------------------------------- 1 | name: Build artifacts and pull images 2 | description: 'Build artifacts and pull images' 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Build and pull 8 | shell: bash 9 | run: | 10 | set -euxo pipefail 11 | make OPT_PROFILE=release build install pull -------------------------------------------------------------------------------- /.github/actions/run-hey-load-test/action.yml: -------------------------------------------------------------------------------- 1 | name: "Run hey load test" 2 | description: "Composite action to run hey load test" 3 | 4 | inputs: 5 | github-token: 6 | description: "GitHub token" 7 | required: true 8 | 9 | runs: 10 | using: "composite" 11 | steps: 12 | - name: Install hey 13 | shell: bash 14 | run: | 15 | curl -LO https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64 16 | chmod +x hey_linux_amd64 17 | sudo mv hey_linux_amd64 /usr/local/bin/hey 18 | hey --help 19 | - name: Run hey benchmark 20 | shell: bash 21 | run: | 22 | hey -n 100000 -c 50 http://127.0.0.1:8080 > raw-output.txt 23 | python3 ./scripts/parse-hey.py raw-output.txt 24 | cat latency_results.json 25 | cat throughput_results.json 26 | - name: Report Throughput results 27 | uses: benchmark-action/github-action-benchmark@v1.20.4 28 | with: 29 | name: "HTTP Throughput" 30 | tool: "customBiggerIsBetter" 31 | output-file-path: throughput_results.json 32 | github-token: ${{ inputs.github-token }} 33 | auto-push: ${{ github.event_name == 'schedule' }} 34 | summary-always: true 35 | alert-threshold: "130%" 36 | fail-on-alert: ${{ github.event_name == 'schedule' }} 37 | - name: Report Latency results 38 | if : ${{ github.event_name == 'schedule' }} 39 | uses: benchmark-action/github-action-benchmark@v1.20.4 40 | with: 41 | name: "HTTP Latency" 42 | tool: "customSmallerIsBetter" 43 | output-file-path: latency_results.json 44 | github-token: ${{ inputs.github-token }} 45 | auto-push: true 46 | summary-always: true 47 | alert-threshold: "130%" 48 | fail-on-alert: true 49 | 50 | # If the event is not a schedule, we'd run into a problem of the failed `git fetch` command, 51 | # which attempts to update the local `gh-pages` branch with changes from the remote repository 52 | # but failed because the update is non-fast-forward. It failed because the previous step 53 | # `benchmark-action/github-action-benchmark` has already committed the changes to the local, so 54 | # it creates a conflict with the remote repository. 55 | # 56 | # The workaround is to use the `external-data-json-path` option to tell the action to not 57 | # attempt to update the local `gh-pages` branch. 58 | - name: Report Latency results 59 | if : ${{ github.event_name != 'schedule' }} 60 | uses: benchmark-action/github-action-benchmark@v1.20.4 61 | with: 62 | name: "HTTP Latency" 63 | tool: "customSmallerIsBetter" 64 | output-file-path: latency_results.json 65 | github-token: ${{ inputs.github-token }} 66 | summary-always: true 67 | alert-threshold: "130%" 68 | fail-on-alert: false 69 | external-data-json-path: ./cache/latency_results.json -------------------------------------------------------------------------------- /.github/actions/setup-env/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Environment 2 | description: 'Common environment setup' 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup OS dependencies 8 | shell: bash 9 | run: ./scripts/setup-$(echo "$RUNNER_OS" | tr '[:upper:]' '[:lower:]').sh -------------------------------------------------------------------------------- /.github/actions/sign/action.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json 2 | 3 | name: Signing 4 | description: 'Signs binaries using cosign' 5 | 6 | inputs: 7 | runtime: 8 | required: true 9 | 10 | runs: 11 | using: "composite" 12 | steps: 13 | - name: Setup cosign for signing 14 | uses: sigstore/cosign-installer@v3.7.0 15 | with: 16 | cosign-release: 'v2.2.2' 17 | - name: Sign the binaries 18 | shell: bash 19 | run: | 20 | # Check if there's any files to archive as tar fails otherwise 21 | if stat dist/bin/* >/dev/null 2>&1; then 22 | echo "::notice::Signing the binary" 23 | cosign sign-blob --yes \ 24 | --output-signature containerd-shim-${{ inputs.runtime }}-v1.sig \ 25 | --output-certificate containerd-shim-${{ inputs.runtime }}-v1.pem \ 26 | --bundle containerd-shim-${{ inputs.runtime }}-v1.bundle \ 27 | dist/bin/containerd-shim-${{ inputs.runtime }}-v1 28 | 29 | # Copy the certs to the dist/bin folder 30 | cp *.sig dist/bin/ 31 | cp *.pem dist/bin/ 32 | else 33 | echo "::warning::No files to sign" 34 | fi -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: cargo 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | groups: 12 | wasmtime-deps: 13 | patterns: 14 | - "wasmtime" 15 | - "wasmtime-wasi" 16 | - "wasi-common" 17 | patch: 18 | update-types: 19 | - "patch" -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Crate labels 2 | C-containerd-shim-wasm: 3 | - changed-files: 4 | - any-glob-to-any-file: crates/containerd-shim-wasm/**/* 5 | 6 | C-wamr: 7 | - changed-files: 8 | - any-glob-to-any-file: crates/containerd-shim-wamr/**/* 9 | 10 | C-wasmedge: 11 | - changed-files: 12 | - any-glob-to-any-file: crates/containerd-shim-wasmedge/**/* 13 | 14 | C-wasmer: 15 | - changed-files: 16 | - any-glob-to-any-file: crates/containerd-shim-wasmer/**/* 17 | 18 | C-wasmtime: 19 | - changed-files: 20 | - any-glob-to-any-file: crates/containerd-shim-wasmtime/**/* 21 | 22 | C-common: 23 | - changed-files: 24 | - any-glob-to-any-file: 25 | - crates/containerd-shim-wasm-test-modules/**/* 26 | - crates/oci-tar-builder/**/* 27 | - crates/wasi-demo-app/**/* 28 | - crates/stress-test/**/* 29 | 30 | # Change type labels 31 | T-CI: 32 | - changed-files: 33 | - any-glob-to-any-file: [".github/**", "scripts/**", "cross/**", "Makefile"] 34 | 35 | T-docs: 36 | - changed-files: 37 | - any-glob-to-any-file: ["docs/**", "*.md", "CONTRIBUTING.md", "RELEASE.md"] 38 | 39 | T-tests: 40 | - changed-files: 41 | - any-glob-to-any-file: ["test/**"] 42 | 43 | T-benchmarks: 44 | - changed-files: 45 | - any-glob-to-any-file: ["benches/**"] -------------------------------------------------------------------------------- /.github/workflows/action-check.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json 2 | 3 | name: Run lint 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | os: 9 | required: true 10 | type: string 11 | runtime: 12 | required: true 13 | type: string 14 | 15 | jobs: 16 | check: 17 | name: lint on ${{ inputs.os }} 18 | runs-on: ${{ inputs.os }} 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: actions-rust-lang/setup-rust-toolchain@v1 22 | with: 23 | components: rustfmt, clippy 24 | rustflags: '' #Disable. By default this action sets environment variable is set to -D warnings. We manage this in the Makefile 25 | - uses: ./.github/actions/setup-env 26 | - run: 27 | # needed to run rustfmt in nightly toolchain 28 | rustup toolchain install nightly --component rustfmt 29 | - name: Run checks 30 | run: make check-${{ inputs.runtime }} 31 | -------------------------------------------------------------------------------- /.github/workflows/action-test-image.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json 2 | 3 | name: Run end to end tests on kind 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | image: 9 | type: string 10 | default: img 11 | 12 | jobs: 13 | test-image: 14 | name: build test ${{ inputs.image }} 15 | runs-on: "ubuntu-latest" 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: build 19 | run: make dist/${{ inputs.image }}.tar 20 | - name: Upload artifacts 21 | uses: actions/upload-artifact@master 22 | with: 23 | name: test-${{ inputs.image }} 24 | path: dist/${{ inputs.image }}.tar 25 | -------------------------------------------------------------------------------- /.github/workflows/action-test-k3s.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json 2 | 3 | name: Run end to end tests on k3s 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | os: 9 | required: true 10 | type: string 11 | runtime: 12 | required: true 13 | type: string 14 | 15 | jobs: 16 | e2e-k3s: 17 | name: e2e k3s test on ${{ inputs.os }} 18 | runs-on: ${{ inputs.os }} 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: ./.github/actions/setup-env 22 | - name: Download artifacts 23 | uses: actions/download-artifact@master 24 | with: 25 | name: containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl 26 | path: dist 27 | - name: Unpack artifats 28 | shell: bash 29 | run: | 30 | mkdir -p dist/bin 31 | tar -xzf dist/containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl.tar.gz -C dist/bin 32 | - name: Download test image 33 | uses: actions/download-artifact@master 34 | with: 35 | name: test-img 36 | path: dist 37 | - name: run 38 | timeout-minutes: 10 39 | run: make test/k3s-${{ inputs.runtime }} 40 | # only runs when the previous step fails 41 | - name: inspect failed pods 42 | if: failure() 43 | run: | 44 | sudo bin/k3s kubectl get pods --all-namespaces 45 | sudo bin/k3s kubectl describe pods --all-namespaces 46 | - name: tar logs 47 | if: failure() 48 | run: | 49 | sudo journalctl -u k3s-runwasi > k3s.log 50 | sudo tar czf k3s-logs-${{ inputs.runtime }}-${{ inputs.os }}.tar.gz -C . k3s.log -C /var/log/pods . -C /var/lib/rancher/k3s/agent/containerd/ containerd.log 51 | sudo chown ${USER}:${USER} k3s-logs-${{ inputs.runtime }}-${{ inputs.os }}.tar.gz 52 | - name: upload logs 53 | if: failure() 54 | uses: actions/upload-artifact@master 55 | with: 56 | name: k3s-logs-${{ inputs.runtime }}-${{ inputs.os }} 57 | path: k3s-logs-${{ inputs.runtime }}-${{ inputs.os }}.tar.gz 58 | - name: cleanup 59 | if: always() 60 | run: make test/k3s/clean 61 | -------------------------------------------------------------------------------- /.github/workflows/action-test-kind.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json 2 | 3 | name: Run end to end tests on kind 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | os: 9 | required: true 10 | type: string 11 | runtime: 12 | required: true 13 | type: string 14 | image: 15 | type: string 16 | default: img 17 | test-command: 18 | type: string 19 | required: true 20 | 21 | jobs: 22 | e2e-kind: 23 | name: e2e kind test on ${{ inputs.os }} with ${{ inputs.image }} 24 | runs-on: ${{ inputs.os }} 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: ./.github/actions/setup-env 28 | - name: Download artifacts 29 | uses: actions/download-artifact@master 30 | with: 31 | name: containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl 32 | path: dist 33 | - name: Unpack artifats 34 | shell: bash 35 | run: | 36 | mkdir -p dist/bin 37 | tar -xzf dist/containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl.tar.gz -C dist/bin 38 | - name: Download test image 39 | uses: actions/download-artifact@master 40 | with: 41 | name: test-${{ inputs.image }} 42 | path: dist 43 | - name: run 44 | timeout-minutes: 10 45 | run: ${{ inputs.test-command }} 46 | # only runs when the previous step fails 47 | - name: inspect failed pods 48 | if: failure() 49 | run: | 50 | kubectl get pods --all-namespaces 51 | kubectl describe pods --all-namespaces 52 | - name: tar logs 53 | if: failure() 54 | run: | 55 | bin/kind export logs ./kind-logs --name containerd-wasm 56 | tar czf kind-logs-${{ inputs.runtime }}-${{ inputs.os }}.tar.gz -C ./kind-logs . 57 | - name: upload logs 58 | if: failure() 59 | uses: actions/upload-artifact@master 60 | with: 61 | name: kind-logs-${{ inputs.runtime }}-${{ inputs.os }} 62 | path: kind-logs-${{ inputs.runtime }}-${{ inputs.os }}.tar.gz 63 | - name: cleanup 64 | if: always() 65 | run: make test/k8s/clean 66 | -------------------------------------------------------------------------------- /.github/workflows/action-test-smoke.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-action.json 2 | 3 | name: Run smoke tests 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | os: 9 | required: true 10 | type: string 11 | runtime: 12 | required: true 13 | type: string 14 | 15 | jobs: 16 | smoke-test: 17 | name: smoke test on ${{ inputs.os }} 18 | runs-on: ${{ inputs.os }} 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: ./.github/actions/setup-env 22 | - name: Download artifacts 23 | uses: actions/download-artifact@master 24 | with: 25 | name: containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl 26 | path: dist 27 | - name: Unpack artifats 28 | shell: bash 29 | run: | 30 | mkdir -p dist/bin 31 | tar -xzf dist/containerd-shim-${{ inputs.runtime }}-x86_64-linux-musl.tar.gz -C dist/bin 32 | - name: Download test image 33 | uses: actions/download-artifact@master 34 | with: 35 | name: test-img 36 | path: dist 37 | - name: Enable OTLP 38 | run: | 39 | sudo ./scripts/setup-jeager-and-otel.sh 40 | - name: Run wasi-demo-app using ctr 41 | timeout-minutes: 5 42 | run: | 43 | ls -alh dist 44 | ls -alh dist/bin 45 | sudo cp -f dist/bin/* /usr/local/bin 46 | make pull-app 47 | sudo ctr run --rm --runtime=io.containerd.${{ inputs.runtime }}.v1 ghcr.io/containerd/runwasi/wasi-demo-app:latest testwasm /wasi-demo-app.wasm echo 'hello' 48 | - name: Verify Jaeger traces 49 | run: | 50 | sleep 5 51 | sudo ./scripts/verify-jaeger-traces.sh -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy mdbook documentation 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | changes: 12 | runs-on: ubuntu-22.04 13 | timeout-minutes: 10 14 | outputs: 15 | dirs: ${{ steps.filter.outputs.changes }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: dorny/paths-filter@v3 19 | id: filter 20 | with: 21 | filters: | 22 | docs: docs/** 23 | deploy: 24 | needs: [changes] 25 | if: ${{ !contains(needs.changes.outputs.dirs, '[]') }} 26 | runs-on: ubuntu-22.04 27 | timeout-minutes: 10 28 | concurrency: 29 | group: ${{ github.workflow }}-${{ github.ref }} 30 | steps: 31 | - uses: actions/checkout@v4 32 | - uses: actions-rust-lang/setup-rust-toolchain@v1 33 | - name: install mermaid preprocessor 34 | run: cargo install mdbook mdbook-mermaid 35 | - name: Build mdbook 36 | working-directory: ./docs 37 | run: mdbook build 38 | - name: Deploy 39 | uses: peaceiris/actions-gh-pages@v4 40 | if: ${{ github.ref == 'refs/heads/main' }} 41 | with: 42 | github_token: ${{ secrets.GITHUB_TOKEN }} 43 | publish_dir: ./docs/book 44 | keep_files: true 45 | 46 | post_deploy_link_check: 47 | name: Verify links after deployment 48 | needs: [deploy] 49 | if: ${{ github.ref == 'refs/heads/main' }} 50 | permissions: 51 | contents: read 52 | issues: write 53 | timeout-minutes: 15 54 | runs-on: ubuntu-latest 55 | steps: 56 | - name: Wait for GitHub Pages to update 57 | run: sleep 120 58 | - name: Check all links on runwasi.dev 59 | uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1 60 | with: 61 | fail: true # Fail CI if broken links found on the live site 62 | format: markdown 63 | jobSummary: true 64 | 65 | - name: Setup GitHub CLI 66 | if: ${{ failure() }} 67 | uses: cli/setup-gh@v1 68 | 69 | - name: Create or update issue on link check failure 70 | if: ${{ failure() }} 71 | shell: bash 72 | env: 73 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 74 | run: | 75 | TODAY=$(date +"%Y-%m-%d") 76 | EXISTING_ISSUE=$(gh issue list --repo ${{ github.repository }} --label broken-links --state open --json number --jq ".[0].number") 77 | ISSUE_BODY="# Broken Links Report ($TODAY) 78 | 79 | The post-deployment link check has detected broken links on the runwasi.dev website. 80 | Please see the [link checker workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. 81 | 82 | > This issue was automatically generated from the link checker workflow." 83 | 84 | if [ -n "$EXISTING_ISSUE" ]; then 85 | gh issue comment $EXISTING_ISSUE --repo ${{ github.repository }} --body "New broken links were detected in the workflow run on $TODAY. Please check the latest workflow run for details: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" 86 | else 87 | gh issue create --repo ${{ github.repository }} --title "Broken links detected on runwasi.dev" --body "$ISSUE_BODY" --label documentation --label broken-links 88 | fi 89 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: PR Labeler 2 | on: 3 | # Runs workflow when activity on a PR in the workflow's repository occurs. 4 | pull_request_target: 5 | 6 | permissions: 7 | contents: read 8 | pull-requests: write 9 | 10 | jobs: 11 | label: 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 5 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/labeler@v5 17 | with: 18 | repo-token: ${{ secrets.GITHUB_TOKEN }} 19 | sync-labels: true -------------------------------------------------------------------------------- /.github/workflows/sbom.yml: -------------------------------------------------------------------------------- 1 | name: Generate SBOMs 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | image-name: 7 | type: string 8 | required: true 9 | image-digest: 10 | type: string 11 | required: true 12 | 13 | jobs: 14 | sbom: 15 | name: Generate SBOM, sign and attach them to OCI image 16 | permissions: 17 | packages: write 18 | id-token: write 19 | 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Sanitize image name 23 | run: | 24 | image="${{ inputs.image-name }}" 25 | image="${image//_/-}" 26 | echo "image=$image" >> $GITHUB_ENV 27 | 28 | - name: Install cosign 29 | uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2 30 | 31 | - name: Install syft 32 | uses: anchore/sbom-action/download-syft@e11c554f704a0b820cbf8c51673f6945e0731532 # v0.20.0 33 | 34 | - name: Login to GitHub Container Registry 35 | uses: docker/login-action@v3 36 | with: 37 | registry: ghcr.io 38 | username: ${{ github.repository }} 39 | password: ${{ secrets.GITHUB_TOKEN }} 40 | 41 | - name: Find repository name 42 | shell: bash 43 | run: | 44 | set -e 45 | IMG_REPOSITORY_NAME=$( echo ${{ github.repository }} | awk '{print tolower($0)}' ) 46 | echo IMG_REPOSITORY_NAME=${IMG_REPOSITORY_NAME} >> $GITHUB_ENV 47 | 48 | - name: Create SBOM file 49 | shell: bash 50 | run: | 51 | SYFT=$(which syft) 52 | sudo $SYFT \ 53 | -o spdx-json \ 54 | --file $image-sbom.spdx \ 55 | ghcr.io/${{ env.IMG_REPOSITORY_NAME }}/$image@${{ inputs.image-digest }} 56 | 57 | - name: Sign SBOM file 58 | run: | 59 | cosign sign-blob --yes \ 60 | --output-certificate $image-sbom.spdx.cert \ 61 | --output-signature $image-sbom.spdx.sig \ 62 | $image-sbom.spdx 63 | 64 | - name: Attach SBOM to container image 65 | shell: bash 66 | run: | 67 | cosign attach \ 68 | sbom --sbom $image-sbom.spdx \ 69 | ghcr.io/${{ env.IMG_REPOSITORY_NAME }}/$image@${{ inputs.image-digest }} 70 | 71 | - name: Sign SBOM file pushed to OCI registry 72 | shell: bash 73 | run: | 74 | set -e 75 | SBOM_TAG="$(echo ${{ inputs.image-digest }} | sed -e 's/:/-/g').sbom" 76 | cosign sign --yes \ 77 | ghcr.io/${{ env.IMG_REPOSITORY_NAME }}/$image:${SBOM_TAG} 78 | 79 | - name: Upload SBOMs as artifacts 80 | uses: actions/upload-artifact@v4 81 | with: 82 | name: sbom 83 | path: $image-sbom-* -------------------------------------------------------------------------------- /.github/workflows/scorecard-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Scorecard analysis workflow 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '30 1 * * 6' # 1:30 AM UTC on Saturday 6 | pull_request: 7 | paths: 8 | - ".github/workflows/scorecard-analysis.yml" 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | analysis: 14 | name: Scorecard analysis 15 | runs-on: ubuntu-latest 16 | permissions: 17 | # Needed for Code scanning upload 18 | security-events: write 19 | # Needed for GitHub OIDC token if publish_results is true 20 | id-token: write 21 | 22 | steps: 23 | - name: "Checkout code" 24 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 25 | with: 26 | persist-credentials: false 27 | 28 | - name: "Run analysis" 29 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 30 | with: 31 | results_file: results.sarif 32 | results_format: sarif 33 | # Publish the results for public repositories to enable scorecard badges. For more details, see 34 | # https://github.com/ossf/scorecard-action#publishing-results. 35 | # For now, we don't want to publish the results 36 | publish_results: false 37 | 38 | - name: "Upload artifact" 39 | uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 40 | with: 41 | name: SARIF file 42 | path: results.sarif 43 | retention-days: 5 44 | 45 | # Upload the results to GitHub's code scanning dashboard 46 | # Commenting out will disable upload of results to your repo's Code Scanning dashboard 47 | - name: "Upload to code-scanning" 48 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 49 | with: 50 | sarif_file: results.sarif -------------------------------------------------------------------------------- /.github/workflows/sign.yml: -------------------------------------------------------------------------------- 1 | name: Sign image 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | image-name: 7 | type: string 8 | required: true 9 | image-digest: 10 | type: string 11 | required: true 12 | 13 | jobs: 14 | sign: 15 | name: Sign image 16 | permissions: 17 | packages: write 18 | id-token: write 19 | 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Sanitize image name 23 | run: | 24 | image="${{ inputs.image-name }}" 25 | image="${image//_/-}" 26 | echo "image=$image" >> $GITHUB_ENV 27 | 28 | - name: Install cosign 29 | uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2 30 | 31 | - name: Login to GitHub Container Registry 32 | uses: docker/login-action@v3 33 | with: 34 | registry: ghcr.io 35 | username: ${{ github.repository }} 36 | password: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | - name: Sign container image 39 | run: | 40 | IMG_REPOSITORY_NAME=$( echo ${{ github.repository }} | awk '{print tolower($0)}' ) 41 | cosign sign --yes \ 42 | ghcr.io/${IMG_REPOSITORY_NAME}/$image@${{ inputs.image-digest }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /dist/ 3 | /test/out/img.tar 4 | /test/k8s/_out 5 | **/target/ 6 | /release/ 7 | /.vscode/ 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/.gitmodules -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/containerd-shim-wasm", 4 | "crates/containerd-shim-wasm-test-modules", 5 | "crates/wasi-demo-app", 6 | "crates/oci-tar-builder", 7 | "crates/containerd-shim-wasmedge", 8 | "crates/containerd-shim-wasmtime", 9 | "crates/containerd-shim-wasmer", 10 | "crates/containerd-shim-wamr", 11 | "crates/containerd-shimkit", 12 | "crates/stress-test", 13 | "benches/containerd-shim-benchmarks", 14 | ] 15 | resolver = "2" 16 | 17 | [workspace.package] 18 | edition = "2024" 19 | version = "0.4.0" 20 | license = "Apache-2.0" 21 | readme = "README.md" 22 | repository = "https://github.com/containerd/runwasi" 23 | homepage = "https://github.com/containerd/runwasi" 24 | 25 | [workspace.dependencies] 26 | anyhow = "1.0" 27 | chrono = { version = "0.4", default-features = false, features = ["clock"] } 28 | containerd-shim = "0.8" 29 | containerd-shimkit = { path = "crates/containerd-shimkit", version = "0.1.1" } 30 | containerd-shim-wasm = { path = "crates/containerd-shim-wasm", version = "1.0.0" } 31 | containerd-shim-wasm-test-modules = { path = "crates/containerd-shim-wasm-test-modules", version = "0.4.0"} 32 | oci-tar-builder = { path = "crates/oci-tar-builder", version = "0.4.0" } 33 | env_logger = "0.11" 34 | libc = "0.2.172" 35 | libcontainer = { version = "0.5", default-features = false } 36 | log = "0.4" 37 | nix = "0.29" 38 | oci-spec = { version = "0.7.1", features = ["runtime"] } 39 | protobuf = "=3.2" 40 | serde = "1.0" 41 | serde_json = "1.0" 42 | sha256 = "1.6.0" 43 | tar = "0.4" 44 | tempfile = "3.19" 45 | thiserror = "2.0" 46 | wat = "1.228" 47 | windows-sys = "0.59" 48 | serial_test = "3" 49 | tracing = "0.1" 50 | hyper = "1.6.0" 51 | tokio = { version = "1.45.1", default-features = false } 52 | tokio-util = { version = "0.7", default-features = false } 53 | cfg-if = "1.0" 54 | 55 | # wasmtime 56 | wasmtime = { version = "27.0.0", features = ["async"] } 57 | wasmtime-wasi = { version = "27.0.0" } 58 | wasmtime-wasi-http = { version = "27.0.0" } 59 | 60 | [profile.release] 61 | panic = "abort" 62 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | default-target = "x86_64-unknown-linux-musl" 3 | 4 | [target.aarch64-unknown-linux-musl] 5 | dockerfile = "cross/Dockerfile.musl" 6 | 7 | [target.x86_64-unknown-linux-musl] 8 | dockerfile = "cross/Dockerfile.musl" 9 | 10 | [target.aarch64-unknown-linux-gnu] 11 | dockerfile = "cross/Dockerfile.gnu" 12 | 13 | [target.x86_64-unknown-linux-gnu] 14 | dockerfile = "cross/Dockerfile.gnu" 15 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | # runwasi maintainers 2 | # 3 | # As a containerd sub-project, containerd maintainers are also included from https://github.com/containerd/project/blob/main/MAINTAINERS. 4 | # See https://github.com/containerd/project/blob/main/GOVERNANCE.md for description of maintainer role 5 | # 6 | # COMMITTERS 7 | # GitHub ID, Name, Email address 8 | "cpuguy","Brian Goff","cpuguy83@gmail.com" 9 | "mossaka","Jiaxiao Zhou","jiazho@microsoft.com" 10 | "danbugs","Dan Chiarlone","danilochiarlone@gmail.com" 11 | "devigned","David Justice","david@justice.dev" 12 | "jprendes","Jorge Prendes","jorge.prendes@gmail.com" 13 | "jsturtevant","James Sturtevant","jstur@microsoft.com" 14 | 15 | # REVIEWERS 16 | # GitHub ID, Name, Email address 17 | "ipuustin","Ismo Puustinen","ismo.puustinen@intel.com" 18 | "rumpl","Djordje Lukic","djordje.lukic@docker.com" 19 | "utam0k","Toru Komatsu","k0ma@utam0k.jp" 20 | "andreiltd","Tomasz Andrzejak","andreiltd@gmail.com" 21 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Docker | Containerd 2 | Copyright 2022 Docker, Inc. 3 | 4 | This product includes software developed at Deis Labs (https://github.com/deislabs) 5 | but ownership transitioned from deislabs to the containerd org since 2022. 6 | 7 | This product includes software developed at 8 | The Containerd (https://github.com/containerd/runwasi). 9 | 10 | The Initial Developer of some parts of the file 11 | crates/containerd-shim-wasm/src/sandbox/shim.rs, which are copied from, 12 | derived from, or inspired by Youki (https://github.com/containers/youki) 13 | Copyright 2021 - 2022 Youki. All Rights Reserved. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | runwasi logo 5 | 6 | 7 |

runwasi

8 |

9 | CI status 10 | crates.io 11 | docs.rs 12 | Downloads 13 | website 14 |

15 |
16 | 17 | This is a project to facilitate running wasm workloads managed by containerd either directly (ie. through ctr) or as directed by Kubelet via the CRI plugin. 18 | It is intended to be a (rust) library that you can take and integrate with your wasm host. 19 | Included in the repository is a PoC for running a plain wasi host (ie. no extra host functions except to support wasi system calls). 20 | 21 | ## Community 22 | 23 | - If you haven't joined the CNCF slack yet, you can do so [here](https://slack.cncf.io/). 24 | - Come join us on our [slack channel #runwasi](https://cloud-native.slack.com/archives/C04LTPB6Z0V) on the CNCF slack. 25 | - Public Community Call on Tuesdays every other week at 9:00 AM PT: [Zoom](https://zoom.us/my/containerd?pwd=bENmREpnSGRNRXdBZWV5UG8wbU1oUT09), [Meeting Notes](https://docs.google.com/document/d/1aOJ-O7fgMyRowHD0kOoA2Z_4d19NyAvvdqOkZO3Su_M/edit?usp=sharing) 26 | 27 | See our [Community Page](https://runwasi.dev/resources/community.html) for more ways to get involved. 28 | 29 | ## Documentation 30 | 31 | For comprehensive documentation, visit our [Documentation Site](https://runwasi.dev/). 32 | 33 | For `containerd-shim-wasm` crate documentation, visit [containerd-shim-wasm](https://docs.rs/containerd-shim-wasm). 34 | 35 | ## Quick Start 36 | 37 | ### Installation 38 | 39 | ```terminal 40 | make build 41 | sudo make install 42 | ``` 43 | 44 | For detailed installation instructions, see the [Installation Guide](https://runwasi.dev/getting-started/installation.html). 45 | 46 | ### Running an Example 47 | 48 | ```terminal 49 | # Pull the image 50 | sudo ctr images pull ghcr.io/containerd/runwasi/wasi-demo-app:latest 51 | 52 | # Run the example 53 | sudo ctr run --rm --runtime=io.containerd.wasmtime.v1 ghcr.io/containerd/runwasi/wasi-demo-app:latest testwasm 54 | ``` 55 | 56 | For more examples and detailed usage, see the [Demos](https://runwasi.dev/getting-started/demos.html). 57 | 58 | ## Projects Using Runwasi 59 | 60 | Check out these projects that build on top of runwasi: 61 | - [spinkube/containerd-shim-spin](https://github.com/spinkube/containerd-shim-spin) 62 | - [deislabs/containerd-wasm-shims](https://github.com/deislabs/containerd-wasm-shims) 63 | 64 | ## Contributing 65 | 66 | To begin contributing, please read our [Contributing Guide](https://runwasi.dev/CONTRIBUTING.html). 67 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | See https://github.com/containerd/project/blob/main/SECURITY.md for reporting a vulnerability. 2 | -------------------------------------------------------------------------------- /benches/containerd-shim-benchmarks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "containerd-shim-benchmarks" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [dev-dependencies] 7 | criterion = { version = "0.5", features = ["html_reports"] } 8 | 9 | [[bench]] 10 | name = "wasi-demo-app-benchmarks" 11 | harness = false 12 | -------------------------------------------------------------------------------- /benches/containerd-shim-benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # Containerd Shim Benchmarks 2 | 3 | This directory contains benchmarks for testing various aspects of the containerd shims: 4 | 5 | ## Running the Benchmarks 6 | 7 | First, build and install the shims: 8 | ```bash 9 | make build 10 | sudo make install 11 | ``` 12 | 13 | Then, pull the wasi-demo-app: 14 | ```bash 15 | make pull 16 | ``` 17 | 18 | To run all benchmarks: 19 | ```bash 20 | cargo bench -p containerd-shim-benchmarks 21 | ``` 22 | 23 | Note: The benchmarks require sudo access to run containerd commands. Make sure you have the necessary permissions configured. 24 | -------------------------------------------------------------------------------- /benches/containerd-shim-benchmarks/benches/wasi-demo-app-benchmarks.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | use std::time::{Duration, Instant}; 3 | 4 | use criterion::{Criterion, criterion_group, criterion_main}; 5 | 6 | struct TestCase<'a> { 7 | image: &'a str, 8 | entrypoint: &'a str, 9 | args: &'a [&'a str], 10 | expected: &'a str, 11 | } 12 | 13 | static RUNTIMES: &[&str] = &["wasmtime", "wasmedge", "wasmer", "wamr"]; 14 | static TEST_CASES: &[TestCase] = &[ 15 | TestCase { 16 | image: "ghcr.io/containerd/runwasi/wasi-demo-app:latest", 17 | entrypoint: "wasi-demo-app.wasm", 18 | args: &["echo", "hello"], 19 | expected: "hello", 20 | }, 21 | TestCase { 22 | image: "ghcr.io/containerd/runwasi/wasi-demo-oci:latest", 23 | entrypoint: "wasi-demo-oci.wasm", 24 | args: &["echo", "hello"], 25 | expected: "hello", 26 | }, 27 | ]; 28 | 29 | fn run_container(runtime: &str, test_case: &TestCase, verify_output: F) -> Duration 30 | where 31 | F: Fn(&str), 32 | { 33 | let start = Instant::now(); 34 | let mut cmd = Command::new("sudo"); 35 | cmd.args([ 36 | "ctr", 37 | "run", 38 | "--rm", 39 | &format!("--runtime=io.containerd.{}.v1", runtime), 40 | test_case.image, 41 | "testwasm", 42 | test_case.entrypoint, 43 | ]); 44 | cmd.args(test_case.args); 45 | 46 | let output = cmd.output().expect("Failed to execute command"); 47 | if !output.status.success() { 48 | panic!( 49 | "Container failed to run: {}", 50 | String::from_utf8_lossy(&output.stderr) 51 | ); 52 | } 53 | 54 | let stdout = String::from_utf8_lossy(&output.stdout); 55 | verify_output(&stdout); 56 | 57 | start.elapsed() 58 | } 59 | 60 | fn benchmark_image(c: &mut Criterion) { 61 | let mut group = c.benchmark_group("end-to-end"); 62 | for runtime in RUNTIMES { 63 | for test_case in TEST_CASES { 64 | let image_base_name = test_case 65 | .image 66 | .rsplit('/') 67 | .next() 68 | .unwrap_or(test_case.image); 69 | let bench_name = format!("{}/{}", runtime, image_base_name); 70 | group.bench_function(&bench_name, |b| { 71 | b.iter(|| { 72 | run_container(runtime, test_case, |stdout| { 73 | assert!(stdout.contains(test_case.expected)); 74 | }) 75 | }); 76 | }); 77 | } 78 | } 79 | group.finish(); 80 | } 81 | 82 | criterion_group! { 83 | name = benches; 84 | config = Criterion::default() 85 | .sample_size(10) 86 | .warm_up_time(Duration::from_secs(3)); 87 | targets = benchmark_image 88 | } 89 | 90 | criterion_main!(benches); 91 | -------------------------------------------------------------------------------- /crates/containerd-shim-wamr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "containerd-shim-wamr" 3 | version = "0.2.0" 4 | edition.workspace = true 5 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 6 | 7 | [dependencies] 8 | anyhow = { workspace = true } 9 | containerd-shim-wasm = { workspace = true, features = ["opentelemetry"] } 10 | log = { workspace = true } 11 | 12 | [target.'cfg(unix)'.dependencies] 13 | wamr-rust-sdk = { git = "https://github.com/bytecodealliance/wamr-rust-sdk", tag = "v1.1.0" } 14 | 15 | [dev-dependencies] 16 | containerd-shim-wasm = { workspace = true, features = ["testing"] } 17 | serial_test = { workspace = true } 18 | 19 | [[bin]] 20 | name = "containerd-shim-wamr-v1" 21 | path = "src/main.rs" 22 | 23 | [package.metadata.cargo-machete] 24 | ignored = ["log"] 25 | -------------------------------------------------------------------------------- /crates/containerd-shim-wamr/src/instance.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use containerd_shim_wasm::sandbox::Sandbox; 3 | use containerd_shim_wasm::sandbox::context::{Entrypoint, RuntimeContext}; 4 | use containerd_shim_wasm::shim::{Shim, Version, version}; 5 | use wamr_rust_sdk::function::Function; 6 | use wamr_rust_sdk::instance::Instance as WamrInst; 7 | use wamr_rust_sdk::module::Module; 8 | use wamr_rust_sdk::runtime::Runtime; 9 | use wamr_rust_sdk::wasi_context::WasiCtxBuilder; 10 | 11 | pub struct WamrShim; 12 | 13 | pub struct WamrSandbox { 14 | runtime: Runtime, 15 | } 16 | 17 | unsafe impl Send for WamrSandbox {} 18 | unsafe impl Sync for WamrSandbox {} 19 | 20 | impl Default for WamrSandbox { 21 | fn default() -> Self { 22 | let runtime = Runtime::new().unwrap(); 23 | Self { runtime } 24 | } 25 | } 26 | 27 | impl Shim for WamrShim { 28 | type Sandbox = WamrSandbox; 29 | 30 | fn name() -> &'static str { 31 | "wamr" 32 | } 33 | 34 | fn version() -> Version { 35 | version!() 36 | } 37 | } 38 | 39 | impl Sandbox for WamrSandbox { 40 | async fn run_wasi(&self, ctx: &impl RuntimeContext) -> Result { 41 | let args = ctx.args(); 42 | let envs = ctx.envs(); 43 | let Entrypoint { 44 | source, func, name, .. 45 | } = ctx.entrypoint(); 46 | 47 | let wasm_bytes = source 48 | .as_bytes() 49 | .context("Failed to get bytes from source")?; 50 | 51 | log::info!("Create a WAMR module"); 52 | 53 | // TODO: error handling isn't ideal 54 | 55 | let mod_name = name.unwrap_or_else(|| "main".to_string()); 56 | 57 | let mut module = Module::from_buf(&self.runtime, &wasm_bytes, &mod_name) 58 | .context("Failed to create module from bytes")?; 59 | 60 | log::info!("Create a WASI context"); 61 | 62 | let wasi_ctx = WasiCtxBuilder::new() 63 | .set_pre_open_path(vec!["/"], vec![]) 64 | .set_env_vars(envs.iter().map(String::as_str).collect()) 65 | .set_arguments(args.iter().map(String::as_str).collect()) 66 | .build(); 67 | 68 | module.set_wasi_context(wasi_ctx); 69 | 70 | // TODO: no way to register a named module with bytes? 71 | 72 | log::info!("Create a WAMR instance"); 73 | 74 | let instance = WamrInst::new(&self.runtime, &module, 1024 * 64) 75 | .context("Failed to create instance")?; 76 | 77 | log::info!("Running {func:?}"); 78 | let function = 79 | Function::find_export_func(&instance, &func).context("Failed to find function")?; 80 | let status = function 81 | .call(&instance, &vec![]) 82 | .map(|_| 0) 83 | .map_err(|err| { 84 | log::error!("Error: {err:?}"); 85 | err 86 | }) 87 | .context("Failed to call function")?; 88 | 89 | Ok(status) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /crates/containerd-shim-wamr/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(unix)] 2 | pub mod instance; 3 | 4 | #[cfg(unix)] 5 | pub use instance::WamrShim; 6 | 7 | #[cfg(unix)] 8 | #[cfg(test)] 9 | #[path = "tests.rs"] 10 | mod wamr_tests; 11 | -------------------------------------------------------------------------------- /crates/containerd-shim-wamr/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(target_os = "windows"))] 2 | use containerd_shim_wamr::WamrShim; 3 | use containerd_shim_wasm::shim::Cli; 4 | 5 | #[cfg(target_os = "windows")] 6 | fn main() { 7 | panic!("WAMR shim is not supported on Windows"); 8 | } 9 | 10 | #[cfg(not(target_os = "windows"))] 11 | fn main() { 12 | WamrShim::run(None); 13 | } 14 | -------------------------------------------------------------------------------- /crates/containerd-shim-wamr/src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use containerd_shim_wasm::testing::WasiTest; 4 | use containerd_shim_wasm::testing::modules::*; 5 | use serial_test::serial; 6 | 7 | use crate::WamrShim as WasiEngine; 8 | 9 | #[test] 10 | #[serial] 11 | fn test_delete_after_create() -> anyhow::Result<()> { 12 | WasiTest::::builder()?.build()?.delete()?; 13 | Ok(()) 14 | } 15 | 16 | #[test] 17 | #[serial] 18 | fn test_hello_world() -> anyhow::Result<()> { 19 | let (exit_code, stdout, _) = WasiTest::::builder()? 20 | .with_wasm(HELLO_WORLD)? 21 | .build()? 22 | .start()? 23 | .wait(Duration::from_secs(10))?; 24 | 25 | assert_eq!(exit_code, 0); 26 | assert_eq!(stdout, "hello world\n"); 27 | 28 | Ok(()) 29 | } 30 | 31 | #[test] 32 | #[serial] 33 | fn test_hello_world_oci() -> anyhow::Result<()> { 34 | let (builder, _oci_cleanup) = WasiTest::::builder()? 35 | .with_wasm(HELLO_WORLD)? 36 | .as_oci_image(None, None)?; 37 | 38 | let (exit_code, stdout, _) = builder.build()?.start()?.wait(Duration::from_secs(10))?; 39 | 40 | assert_eq!(exit_code, 0); 41 | assert_eq!(stdout, "hello world\n"); 42 | 43 | Ok(()) 44 | } 45 | #[test] 46 | #[serial] 47 | fn test_unreachable() -> anyhow::Result<()> { 48 | let (exit_code, _, _) = WasiTest::::builder()? 49 | .with_wasm(UNREACHABLE)? 50 | .build()? 51 | .start()? 52 | .wait(Duration::from_secs(10))?; 53 | 54 | assert_ne!(exit_code, 0); 55 | 56 | Ok(()) 57 | } 58 | 59 | #[test] 60 | #[serial] 61 | fn test_seccomp() -> anyhow::Result<()> { 62 | let (exit_code, stdout, _) = WasiTest::::builder()? 63 | .with_wasm(SECCOMP)? 64 | .build()? 65 | .start()? 66 | .wait(Duration::from_secs(10))?; 67 | 68 | assert_eq!(exit_code, 0); 69 | assert_eq!(stdout.trim(), "current working dir: /"); 70 | 71 | Ok(()) 72 | } 73 | 74 | #[test] 75 | #[serial] 76 | fn test_has_default_devices() -> anyhow::Result<()> { 77 | let (exit_code, _, _) = WasiTest::::builder()? 78 | .with_wasm(HAS_DEFAULT_DEVICES)? 79 | .build()? 80 | .start()? 81 | .wait(Duration::from_secs(10))?; 82 | 83 | assert_eq!(exit_code, 0); 84 | 85 | Ok(()) 86 | } 87 | 88 | #[test] 89 | #[ignore = "disabled because the WAMR SDK doesn't expose exit code yet"] 90 | // See https://github.com/containerd/runwasi/pull/716#discussion_r1827086060 91 | fn test_exit_code() -> anyhow::Result<()> { 92 | let (exit_code, _, _) = WasiTest::::builder()? 93 | .with_wasm(EXIT_CODE)? 94 | .build()? 95 | .start()? 96 | .wait(Duration::from_secs(10))?; 97 | 98 | assert_eq!(exit_code, 42); 99 | 100 | Ok(()) 101 | } 102 | 103 | #[test] 104 | #[ignore] 105 | // See https://github.com/containerd/runwasi/pull/716#issuecomment-2458200081 106 | fn test_custom_entrypoint() -> anyhow::Result<()> { 107 | let (exit_code, stdout, _) = WasiTest::::builder()? 108 | .with_start_fn("foo") 109 | .with_wasm(CUSTOM_ENTRYPOINT)? 110 | .build()? 111 | .start()? 112 | .wait(Duration::from_secs(10))?; 113 | 114 | assert_eq!(exit_code, 0); 115 | assert_eq!(stdout, "hello world\n"); 116 | 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "containerd-shim-wasm-test-modules" 3 | description = "Set of WebAssembly modules for testing containerd shims for wasm" 4 | version = "0.4.1" 5 | edition.workspace = true 6 | license.workspace = true 7 | 8 | [build-dependencies] 9 | anyhow = { workspace = true } 10 | lazy_static = "1.5.0" 11 | wat = { workspace = true } 12 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::path::{Path, PathBuf}; 3 | use std::process::Command; 4 | 5 | use anyhow::{Context, Result, bail}; 6 | use lazy_static::lazy_static; 7 | 8 | fn env_path(key: impl AsRef) -> Result { 9 | std::env::var_os(key.as_ref()) 10 | .map(Into::into) 11 | .with_context(|| format!("failed to read env-var {}", key.as_ref())) 12 | } 13 | 14 | lazy_static! { 15 | static ref OUT_DIR: PathBuf = env_path("OUT_DIR").unwrap(); 16 | static ref PKG_DIR: PathBuf = env_path("CARGO_MANIFEST_DIR").unwrap(); 17 | } 18 | 19 | fn main() -> Result<()> { 20 | let modules_file = OUT_DIR.join("modules.rs"); 21 | let modules_dir = PKG_DIR.join("src").join("modules"); 22 | 23 | let mut writer = std::fs::File::create(modules_file)?; 24 | 25 | let paths = std::fs::read_dir(modules_dir)?; 26 | for entry in paths.flatten() { 27 | let src = entry.path(); 28 | let name = path_to_ident(&src)?.to_ascii_uppercase(); 29 | 30 | println!("cargo:rerun-if-changed={}", src.to_string_lossy()); 31 | 32 | writeln!(writer, "pub const {name}: TestModule = TestModule {{")?; 33 | let dst = match src 34 | .extension() 35 | .unwrap_or_default() 36 | .to_str() 37 | .unwrap_or_default() 38 | { 39 | "rs" => { 40 | writeln!(writer, " source: Some(include_str!({src:?})),")?; 41 | compile_rust(&src)? 42 | } 43 | "wat" => { 44 | writeln!(writer, " source: Some(include_str!({src:?})),")?; 45 | compile_wat(&src)? 46 | } 47 | "wasm" => { 48 | writeln!(writer, " source: None,")?; 49 | move_wasm(&src)? 50 | } 51 | _ => bail!("unrecognized file format for source file {src:?}"), 52 | }; 53 | 54 | writeln!(writer, " bytes: include_bytes!({dst:?}),")?; 55 | writeln!(writer, "}};")?; 56 | } 57 | 58 | Ok(()) 59 | } 60 | 61 | fn compile_rust(src: impl AsRef) -> Result { 62 | let rustc = std::env::var_os("RUSTC").context("reading RUSTC")?; 63 | let src = src.as_ref(); 64 | let dst = output_for(src)?; 65 | 66 | Command::new(rustc) 67 | .arg("--target=wasm32-wasip1") 68 | .arg("-Copt-level=z") 69 | .arg("-Cstrip=symbols") 70 | .arg("-o") 71 | .arg(&dst) 72 | .arg(src) 73 | .spawn()? 74 | .wait()? 75 | .success() 76 | .then_some(dst) 77 | .context("running rustc") 78 | } 79 | 80 | fn compile_wat(src: impl AsRef) -> Result { 81 | let src = src.as_ref(); 82 | let dst = output_for(src)?; 83 | 84 | let bytes = wat::parse_file(src)?; 85 | std::fs::write(&dst, bytes)?; 86 | 87 | Ok(dst) 88 | } 89 | 90 | fn move_wasm(src: impl AsRef) -> Result { 91 | let src = src.as_ref(); 92 | let dst = output_for(src)?; 93 | 94 | std::fs::copy(src, &dst)?; 95 | 96 | Ok(dst) 97 | } 98 | 99 | fn output_for(src: impl AsRef) -> Result { 100 | let src = src.as_ref(); 101 | let filename = src 102 | .file_name() 103 | .with_context(|| format!("getting filename of {src:?}"))?; 104 | Ok(OUT_DIR.join(filename).with_extension("wasm")) 105 | } 106 | 107 | fn path_to_ident(path: impl AsRef) -> Result { 108 | let path = path.as_ref(); 109 | let ident: String = path 110 | .file_stem() 111 | .with_context(|| format!("getting filename of {path:?}"))? 112 | .to_str() 113 | .context("converting filename to string")? 114 | .chars() 115 | .map(|c| match c { 116 | 'A'..='Z' | 'a'..='z' | '0'..='9' => c, 117 | _ => '_', 118 | }) 119 | .collect(); 120 | 121 | if !ident.starts_with(char::is_alphabetic) { 122 | bail!("please start the filename with [a-zA-Z]") 123 | } 124 | 125 | Ok(ident) 126 | } 127 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub struct TestModule { 2 | pub source: Option<&'static str>, 3 | pub bytes: &'static [u8], 4 | } 5 | 6 | impl AsRef<[u8]> for TestModule { 7 | fn as_ref(&self) -> &[u8] { 8 | self.bytes 9 | } 10 | } 11 | 12 | include!(concat!(env!("OUT_DIR"), "/modules.rs")); 13 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/modules/component-hello-world.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/crates/containerd-shim-wasm-test-modules/src/modules/component-hello-world.wasm -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/modules/custom_entrypoint.wat: -------------------------------------------------------------------------------- 1 | (module 2 | ;; Import the required fd_write WASI function which will write the given io vectors to stdout 3 | ;; The function signature for fd_write is: 4 | ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written 5 | (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32))) 6 | 7 | (memory 1) 8 | (export "memory" (memory 0)) 9 | 10 | ;; Write 'hello world\n' to memory at an offset of 8 bytes 11 | ;; Note the trailing newline which is required for the text to appear 12 | (data (i32.const 8) "hello world\n") 13 | 14 | (func $main (export "foo") 15 | ;; Creating a new io vector within linear memory 16 | (i32.store (i32.const 0) (i32.const 8)) ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string 17 | (i32.store (i32.const 4) (i32.const 12)) ;; iov.iov_len - The length of the 'hello world\n' string 18 | 19 | (call $fd_write 20 | (i32.const 1) ;; file_descriptor - 1 for stdout 21 | (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0 22 | (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one. 23 | (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written 24 | ) 25 | drop ;; Discard the number of bytes written from the top of the stack 26 | ) 27 | ) 28 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/modules/exit_code.wat: -------------------------------------------------------------------------------- 1 | (module 2 | ;; Import the required proc_exit WASI function which terminates the program with an exit code. 3 | ;; The function signature for proc_exit is: 4 | ;; (exit_code: i32) -> ! 5 | (import "wasi_snapshot_preview1" "proc_exit" (func $proc_exit (param i32))) 6 | (memory 1) 7 | (export "memory" (memory 0)) 8 | (func $main (export "_start") 9 | (call $proc_exit (i32.const 42)) 10 | unreachable 11 | ) 12 | ) 13 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/modules/fetch_rs.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/crates/containerd-shim-wasm-test-modules/src/modules/fetch_rs.wasm -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/modules/has_default_devices.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | fn main() { 4 | // Runtime must supply at least the following files regardless of OCI devices setting: 5 | let devices = [ 6 | "/dev/null", 7 | "/dev/zero", 8 | "/dev/full", 9 | "/dev/random", 10 | "/dev/urandom", 11 | "/dev/tty", 12 | ]; 13 | 14 | for device in devices.iter() { 15 | if Path::new(device).exists() { 16 | println!("{device} found"); 17 | } else { 18 | panic!("{device} not found"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/modules/hello_wasi_http.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/crates/containerd-shim-wasm-test-modules/src/modules/hello_wasi_http.wasm -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/modules/hello_wasi_http_csharp.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/crates/containerd-shim-wasm-test-modules/src/modules/hello_wasi_http_csharp.wasm -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/modules/hello_world.wat: -------------------------------------------------------------------------------- 1 | (module 2 | ;; Import the required fd_write WASI function which will write the given io vectors to stdout 3 | ;; The function signature for fd_write is: 4 | ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written 5 | (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32))) 6 | 7 | (memory 1) 8 | (export "memory" (memory 0)) 9 | 10 | ;; Write 'hello world\n' to memory at an offset of 8 bytes 11 | ;; Note the trailing newline which is required for the text to appear 12 | (data (i32.const 8) "hello world\n") 13 | 14 | (func $main (export "_start") 15 | ;; Creating a new io vector within linear memory 16 | (i32.store (i32.const 0) (i32.const 8)) ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string 17 | (i32.store (i32.const 4) (i32.const 12)) ;; iov.iov_len - The length of the 'hello world\n' string 18 | 19 | (call $fd_write 20 | (i32.const 1) ;; file_descriptor - 1 for stdout 21 | (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0 22 | (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one. 23 | (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written 24 | ) 25 | drop ;; Discard the number of bytes written from the top of the stack 26 | ) 27 | ) 28 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/modules/seccomp.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Add current working dir request so that we have some known system call to 3 | // test seccomp with. 4 | let cwd = std::env::current_dir().unwrap(); 5 | 6 | println!( 7 | "current working dir: {}", 8 | cwd.to_string_lossy() 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/modules/simple_component.wat: -------------------------------------------------------------------------------- 1 | (component 2 | (core module $m 3 | (func (export "thunk")) 4 | (func (export "thunk-trap") unreachable) 5 | ) 6 | (core instance $i (instantiate $m)) 7 | (func (export "thunk") 8 | (canon lift (core func $i "thunk")) 9 | ) 10 | (func (export "thunk-trap") 11 | (canon lift (core func $i "thunk-trap")) 12 | ) 13 | ) -------------------------------------------------------------------------------- /crates/containerd-shim-wasm-test-modules/src/modules/unreachable.wat: -------------------------------------------------------------------------------- 1 | (func $main (export "_start") 2 | (unreachable) 3 | ) 4 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "containerd-shim-wasm" 3 | description = "Library for building containerd shims for wasm" 4 | version = "1.0.0" 5 | edition.workspace = true 6 | license.workspace = true 7 | readme = "README.md" 8 | homepage.workspace = true 9 | repository.workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | 14 | [dependencies] 15 | anyhow = { workspace = true } 16 | chrono = { workspace = true } 17 | containerd-shimkit = { workspace = true } 18 | containerd-shim = { workspace = true } 19 | containerd-shim-wasm-test-modules = { workspace = true, optional = true } 20 | oci-tar-builder = { workspace = true, optional = true } 21 | env_logger = { workspace = true, optional = true } 22 | libc = { workspace = true } 23 | log = { workspace = true } 24 | oci-spec = { workspace = true } 25 | serde = { workspace = true } 26 | serde_json = { workspace = true } 27 | tempfile = { workspace = true, optional = true } 28 | wat = { workspace = true } 29 | tokio = { workspace = true, features = ["full"] } 30 | futures = { version = "0.3.30" } 31 | wasmparser = { version = "0.231.0" } 32 | tokio-stream = { version = "0.1" } 33 | sha256 = { workspace = true } 34 | serde_bytes = "0.11" 35 | tokio-async-drop = "0.1" 36 | trait-variant = "0.1" 37 | 38 | # tracing 39 | # note: it's important to keep the version of tracing in sync with containerd-shimkit's tracing-subscriber 40 | tracing = { workspace = true, optional = true } 41 | 42 | [target.'cfg(unix)'.dependencies] 43 | caps = "0.5" 44 | # this must match the version pulled by libcontainer 45 | dbus = { version = "0", features = ["vendored"] } 46 | libcontainer = { workspace = true, features = [ 47 | "libseccomp", 48 | "systemd", 49 | "v1", 50 | "v2", 51 | ] } 52 | nix = { workspace = true, features = ["sched", "mount"] } 53 | containerd-client = "0.6.0" 54 | 55 | [target.'cfg(windows)'.dependencies] 56 | windows-sys = { workspace = true, features = [ 57 | "Win32_Foundation", 58 | "Win32_Storage_FileSystem", 59 | ] } 60 | # vendored code 61 | mio = { version = "1.0", features = ["os-ext", "os-poll"] } 62 | 63 | [build-dependencies] 64 | ttrpc-codegen = { version = "0.4.2" } 65 | 66 | [dev-dependencies] 67 | tokio = { workspace = true, features = ["signal"] } 68 | containerd-shim-wasm-test-modules = { workspace = true } 69 | env_logger = { workspace = true } 70 | tempfile = { workspace = true } 71 | oci-tar-builder = { workspace = true } 72 | rand = "0.9" 73 | temp-env = "0.3" 74 | ctor = "0.4.2" 75 | 76 | [features] 77 | testing = [ 78 | "dep:containerd-shim-wasm-test-modules", 79 | "dep:env_logger", 80 | "dep:tempfile", 81 | "dep:oci-tar-builder", 82 | ] 83 | opentelemetry = ["containerd-shimkit/opentelemetry"] 84 | tracing = ["dep:tracing", "containerd-shimkit/tracing"] 85 | 86 | [package.metadata.cargo-machete] 87 | # used as part of a derive macro 88 | ignored = ["serde_bytes"] 89 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/README.md: -------------------------------------------------------------------------------- 1 | ![runwasi logo](https://raw.githubusercontent.com/containerd/runwasi/e251de3307bbdc8bf3229020ea2ae2711f31aafa/art/logo/runwasi_logo_icon.svg) 2 | 3 | # containerd-shim-wasm 4 | 5 | A library to help build containerd shims for Wasm workloads. 6 | 7 | ## Usage 8 | 9 | To implement a shim, simply implement the `Shim` and `Sandbox` trait: 10 | 11 | ```rust,no_run 12 | use containerd_shim_wasm::{ 13 | shim::{Shim, Config, Cli, Version, version}, 14 | sandbox::Sandbox, 15 | sandbox::context::RuntimeContext, 16 | }; 17 | use anyhow::Result; 18 | 19 | struct MyShim; 20 | 21 | #[derive(Default)] 22 | struct MySandbox; 23 | 24 | impl Shim for MyShim { 25 | type Sandbox = MySandbox; 26 | 27 | fn name() -> &'static str { 28 | "my-shim" 29 | } 30 | 31 | fn version() -> Version { 32 | version!() 33 | } 34 | } 35 | 36 | impl Sandbox for MySandbox { 37 | async fn run_wasi(&self, ctx: &impl RuntimeContext) -> Result { 38 | // Implement your Wasm runtime logic here 39 | Ok(0) 40 | } 41 | } 42 | 43 | MyShim::run(None); 44 | ``` 45 | 46 | The `Engine` trait provides optional methods you can override: 47 | 48 | - `can_handle()` - Validates that the runtime can run the container (checks Wasm file headers by default) 49 | - `supported_layers_types()` - Returns supported OCI layer types 50 | - `precompile()` - Allows precompilation of Wasm modules 51 | - `can_precompile()` - Indicates if the runtime supports precompilation 52 | 53 | The resulting shim uses [Youki](https://github.com/youki-dev/youki)'s `libcontainer` crate to manage the container lifecycle, such as creating the container, starting it, and deleting it, and youki handles container sandbox for you. 54 | 55 | ### Running the shim 56 | 57 | containerd expects the shim binary to be installed into `$PATH` (as seen by the containerd process) with a binary name like `containerd-shim-myshim-v1` which maps to the `io.containerd.myshim.v1` runtime. It can be [configured in containerd](https://github.com/containerd/containerd/blob/main/core/runtime/v2/README.md#configuring-runtimes). 58 | 59 | This crate is not tied to any specific wasm engine. 60 | 61 | Check out these projects that build on top of runwasi: 62 | - [spinframework/containerd-shim-spin](https://github.com/spinframework/containerd-shim-spin) 63 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/containerd/lease.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | use std::mem::ManuallyDrop; 4 | 5 | use anyhow::Context as _; 6 | use containerd_client::services::v1::DeleteRequest; 7 | use containerd_client::services::v1::leases_client::LeasesClient; 8 | use containerd_client::tonic::transport::Channel; 9 | use containerd_client::{tonic, with_namespace}; 10 | use tokio_async_drop::tokio_async_drop; 11 | use tonic::Request; 12 | 13 | #[derive(Debug)] 14 | pub(crate) struct LeaseGuard { 15 | inner: Option, 16 | } 17 | 18 | #[derive(Debug)] 19 | pub(crate) struct LeaseGuardInner { 20 | client: LeasesClient, 21 | req: tonic::Request, 22 | } 23 | 24 | impl LeaseGuard { 25 | pub fn new( 26 | client: LeasesClient, 27 | id: impl Into, 28 | namespace: impl AsRef, 29 | ) -> Self { 30 | let id = id.into(); 31 | let req = DeleteRequest { id, sync: false }; 32 | let req = with_namespace!(req, namespace.as_ref()); 33 | let inner = Some(LeaseGuardInner { client, req }); 34 | Self { inner } 35 | } 36 | 37 | // Release a LeaseGuard in a way that we can await for it to complete. 38 | // The alternative to `release` is to `drop` the LeaseGuard, but in that case we can't await for its completion. 39 | pub async fn release(self) -> anyhow::Result<()> { 40 | let mut this = ManuallyDrop::new(self); 41 | this.inner.take().unwrap().release().await?; 42 | Ok(()) 43 | } 44 | 45 | pub fn id(&self) -> &'_ str { 46 | &self.inner.as_ref().unwrap().req.get_ref().id 47 | } 48 | } 49 | 50 | impl LeaseGuardInner { 51 | async fn release(mut self) -> anyhow::Result<()> { 52 | self.client 53 | .delete(self.req) 54 | .await 55 | .context("Failed to remove lease")?; 56 | Ok(()) 57 | } 58 | } 59 | 60 | // Provides a best effort for dropping a lease of the content. If the lease cannot be dropped, it will log a warning 61 | impl Drop for LeaseGuard { 62 | fn drop(&mut self) { 63 | let inner = self.inner.take().unwrap(); 64 | tokio_async_drop!({ 65 | match inner.release().await { 66 | Ok(()) => log::info!("removed lease"), 67 | Err(err) => log::warn!("error removing lease: {err}"), 68 | } 69 | }); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/containerd/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | mod client; 4 | mod lease; 5 | 6 | pub(crate) use client::Client; 7 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![doc( 3 | html_logo_url = "https://raw.githubusercontent.com/containerd/runwasi/e251de3307bbdc8bf3229020ea2ae2711f31aafa/art/logo/runwasi_logo_icon.svg" 4 | )] 5 | 6 | pub mod sandbox; 7 | pub mod shim; 8 | 9 | #[cfg_attr(unix, path = "sys/unix/mod.rs")] 10 | #[cfg_attr(windows, path = "sys/windows/mod.rs")] 11 | pub(crate) mod sys; 12 | 13 | #[cfg(any(test, feature = "testing"))] 14 | /// Utilities for writing shims tests. 15 | /// You can use this to test your runwasi based shim. 16 | pub mod testing; 17 | 18 | #[cfg(test)] 19 | /// Tests for runwasi's containerd-shim-wasm. 20 | mod test; 21 | 22 | pub(crate) mod containerd; 23 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/sandbox/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | 4 | use anyhow::{Context, Result}; 5 | use context::{RuntimeContext, Source}; 6 | use path::PathResolve as _; 7 | 8 | pub mod context; 9 | pub(crate) mod path; 10 | 11 | #[trait_variant::make(Send)] 12 | pub trait Sandbox: Default + 'static { 13 | /// Run a WebAssembly container 14 | async fn run_wasi(&self, ctx: &impl RuntimeContext) -> Result; 15 | 16 | /// Check that the runtime can run the container. 17 | /// This checks runs after the container creation and before the container starts. 18 | /// By default it checks that the wasi_entrypoint is either: 19 | /// * a OCI image with wasm layers 20 | /// * a file with the `wasm` filetype header 21 | /// * a parsable `wat` file. 22 | async fn can_handle(&self, ctx: &impl RuntimeContext) -> Result<()> { 23 | // this async block is required to make the rewrite of trait_variant happy 24 | async move { 25 | let source = ctx.entrypoint().source; 26 | 27 | let path = match source { 28 | Source::File(path) => path, 29 | Source::Oci(_) => return Ok(()), 30 | }; 31 | 32 | path.resolve_in_path_or_cwd() 33 | .next() 34 | .context("module not found")?; 35 | 36 | let mut buffer = [0; 4]; 37 | File::open(&path)?.read_exact(&mut buffer)?; 38 | 39 | if buffer.as_slice() != b"\0asm" { 40 | // Check if this is a `.wat` file 41 | wat::parse_file(&path)?; 42 | } 43 | 44 | Ok(()) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/sandbox/path.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | /// PathResolve allows to resolve a file path in a set of directories. 4 | pub(crate) trait PathResolve { 5 | /// Resolve the path of a file give a set of directories as the `which` unix 6 | /// command would do with components of the `PATH` environment variable, and 7 | /// return an iterator over all candidates. 8 | /// Resulting candidates are files that exist, but no other constraint is 9 | /// imposed, in particular this function does not check for the executable bits. 10 | /// Further constraints can be added by calling filtering the returned iterator. 11 | fn resolve_in_dirs( 12 | &self, 13 | dirs: impl IntoIterator>, 14 | ) -> impl Iterator; 15 | /// Like `resolve_in_dirs`, but searches on the entries of `PATH`. 16 | fn resolve_in_path(&self) -> impl Iterator; 17 | /// Like `resolve_in_dirs`, but searches on the entries of `PATH`, and on `cwd`, in that order. 18 | fn resolve_in_path_or_cwd(&self) -> impl Iterator; 19 | } 20 | 21 | /// Gets the content of the `PATH` environment variable as an iterator over its components 22 | pub(crate) fn paths() -> impl Iterator { 23 | std::env::var_os("PATH") 24 | .as_ref() 25 | .map(std::env::split_paths) 26 | .into_iter() 27 | .flatten() 28 | .collect::>() 29 | .into_iter() 30 | } 31 | 32 | impl> PathResolve for T { 33 | fn resolve_in_dirs( 34 | &self, 35 | dirs: impl IntoIterator>, 36 | ) -> impl Iterator { 37 | let cwd = std::env::current_dir().ok(); 38 | 39 | let has_separator = self.as_ref().components().count() > 1; 40 | 41 | // The seemingly extra complexity here is because we can only have one concrete 42 | // return type even if we return an `impl Iterator` 43 | let (first, second) = if has_separator { 44 | // file has a separator, we only need to rºesolve relative to `cwd`, we must ignore `PATH` 45 | (cwd, None) 46 | } else { 47 | // file is just a binary name, we must not resolve relative to `cwd`, but relative to `PATH` components 48 | let dirs = dirs.into_iter().filter_map(move |p| { 49 | let path = cwd.as_ref()?.join(p.as_ref()).canonicalize().ok()?; 50 | path.is_dir().then_some(path) 51 | }); 52 | (None, Some(dirs)) 53 | }; 54 | 55 | let file = self.as_ref().to_owned(); 56 | first 57 | .into_iter() 58 | .chain(second.into_iter().flatten()) 59 | .filter_map(move |p| { 60 | // skip any paths that are not files 61 | let path = p.join(&file).canonicalize().ok()?; 62 | path.is_file().then_some(path) 63 | }) 64 | } 65 | 66 | // Like `find_in_dirs`, but searches on the entries of `PATH`. 67 | fn resolve_in_path(&self) -> impl Iterator { 68 | self.resolve_in_dirs(paths()) 69 | } 70 | 71 | // Like `find_in_dirs`, but searches on the entries of `PATH`, and on `cwd`, in that order. 72 | fn resolve_in_path_or_cwd(&self) -> impl Iterator { 73 | self.resolve_in_dirs(paths().chain(std::env::current_dir().ok())) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/shim/cli.rs: -------------------------------------------------------------------------------- 1 | //! Command line interface for the containerd shim. 2 | //! 3 | //! The CLI provides the interface between containerd and the Wasm runtime. 4 | //! It handles commands like start and delete from containerd's shim API. 5 | //! 6 | //! ## Usage 7 | //! 8 | //! The shim binary should be named `containerd-shim--v1` and installed in $PATH. 9 | //! containerd will call the shim with various commands. 10 | //! 11 | //! ## Configuration 12 | //! 13 | //! The shim can be configured using the [`Config`] struct: 14 | //! 15 | //! ```rust, no_run 16 | //! use containerd_shim_wasm::shim::Config; 17 | //! 18 | //! let config = Config { 19 | //! // Disable automatic logger setup 20 | //! no_setup_logger: false, 21 | //! // Set default log level 22 | //! default_log_level: "info".to_string(), 23 | //! }; 24 | //! ``` 25 | //! 26 | //! ## Example usage: 27 | //! 28 | //! ```rust, no_run 29 | //! use containerd_shim_wasm::{ 30 | //! shim::{Shim, Cli, Config, Version, version}, 31 | //! sandbox::Sandbox, 32 | //! sandbox::context::RuntimeContext, 33 | //! }; 34 | //! use anyhow::Result; 35 | //! 36 | //! struct MyShim; 37 | //! 38 | //! #[derive(Default)] 39 | //! struct MySandbox; 40 | //! 41 | //! impl Shim for MyShim { 42 | //! type Sandbox = MySandbox; 43 | //! 44 | //! fn name() -> &'static str { 45 | //! "my-shim" 46 | //! } 47 | //! 48 | //! fn version() -> Version { 49 | //! version!() 50 | //! } 51 | //! } 52 | //! 53 | //! impl Sandbox for MySandbox { 54 | //! async fn run_wasi(&self, ctx: &impl RuntimeContext) -> Result { 55 | //! Ok(0) 56 | //! } 57 | //! } 58 | //! 59 | //! let config = Config { 60 | //! default_log_level: "error".to_string(), 61 | //! ..Default::default() 62 | //! }; 63 | //! 64 | //! MyShim::run(config); 65 | //! ``` 66 | //! 67 | //! When the `opentelemetry` feature is enabled, additional runtime config 68 | //! is available through environment variables: 69 | //! 70 | //! - `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`: Enable OpenTelemetry tracing 71 | //! - `OTEL_EXPORTER_OTLP_ENDPOINT`: Enable OpenTelemetry tracing as above 72 | //! - `OTEL_SDK_DISABLED`: Disable OpenTelemetry SDK 73 | //! 74 | 75 | use crate::shim::{Config, Instance, Shim}; 76 | 77 | mod private { 78 | pub trait Sealed {} 79 | } 80 | 81 | impl private::Sealed for S {} 82 | 83 | pub trait Cli: Shim + private::Sealed { 84 | /// Main entry point for the shim. 85 | /// 86 | /// If the `opentelemetry` feature is enabled, this function will start the shim with OpenTelemetry tracing. 87 | /// 88 | /// It parses OTLP configuration from the environment and initializes the OpenTelemetry SDK. 89 | fn run(config: impl Into>); 90 | } 91 | 92 | impl Cli for S { 93 | fn run(config: impl Into>) { 94 | let config = config.into().unwrap_or_default(); 95 | let config = containerd_shimkit::Config { 96 | no_setup_logger: config.no_setup_logger, 97 | default_log_level: config.default_log_level, 98 | no_reaper: false, 99 | no_sub_reaper: false, 100 | }; 101 | containerd_shimkit::sandbox::cli::shim_main::>( 102 | S::name(), 103 | S::version(), 104 | Some(config), 105 | ) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/shim/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains an API for building WebAssembly shims running on top of containers. 2 | //! Unlike the `sandbox` module, this module delegates many of the actions to the container runtime. 3 | //! 4 | //! This has some advantages: 5 | //! * Simplifies writing new shims, get you up and running quickly 6 | //! * The complexity of the OCI spec is already taken care of 7 | //! 8 | //! But it also has some disadvantages: 9 | //! * Runtime overhead in in setting up a container 10 | //! * Less customizable 11 | //! * Currently only works on Linux 12 | //! 13 | //! ## Key Components 14 | //! 15 | //! - [`Shim`]: The trait for implementing the shim entrypoint 16 | //! - [`Sandbox`](crate::sandbox::Sandbox): The core trait for implementing Wasm runtimes 17 | //! - [`RuntimeContext`](crate::sandbox::context::RuntimeContext): The context for running WASI modules 18 | //! 19 | //! ## Version Information 20 | //! 21 | //! The module provides two macros for version information: 22 | //! 23 | //! - [`version!()`](crate::shim::version) - Returns the crate version from Cargo.toml and 24 | //! Git revision hash, if available. 25 | //! 26 | //! ## Example Usage 27 | //! 28 | //! ```rust 29 | //! use containerd_shim_wasm::shim::Shim; 30 | //! use containerd_shim_wasm::sandbox::Sandbox; 31 | //! use containerd_shim_wasm::sandbox::context::RuntimeContext; 32 | //! use anyhow::Result; 33 | //! 34 | //! struct MyShim; 35 | //! 36 | //! #[derive(Default)] 37 | //! struct MySandbox; 38 | //! 39 | //! impl Shim for MyShim { 40 | //! type Sandbox = MySandbox; 41 | //! 42 | //! fn name() -> &'static str { 43 | //! "my-shim" 44 | //! } 45 | //! } 46 | //! 47 | //! impl Sandbox for MySandbox { 48 | //! async fn run_wasi(&self, ctx: &impl RuntimeContext) -> Result { 49 | //! let args = ctx.args(); 50 | //! let envs = ctx.envs(); 51 | //! let entrypoint = ctx.entrypoint(); 52 | //! 53 | //! Ok(0) 54 | //! } 55 | //! } 56 | //! ``` 57 | 58 | #[allow(clippy::module_inception)] 59 | mod shim; 60 | 61 | pub(crate) use instance::Instance; 62 | pub use shim::{Compiler, Shim, Version}; 63 | 64 | use crate::sys::container::instance; 65 | 66 | #[cfg(test)] 67 | mod tests; 68 | 69 | // This is used in containerd::Client tests 70 | #[cfg(test)] 71 | pub(crate) use shim::NO_COMPILER; 72 | 73 | pub(crate) mod cli; 74 | 75 | pub use cli::Cli; 76 | pub use containerd_shimkit::shim_version as version; 77 | 78 | /// Config of shim binary options provided by shim implementations 79 | #[derive(Debug)] 80 | pub struct Config { 81 | /// Disables automatic configuration to use the shim FIFO 82 | pub no_setup_logger: bool, 83 | /// Sets the the default log level. Default is info 84 | pub default_log_level: String, 85 | } 86 | 87 | impl Default for Config { 88 | fn default() -> Self { 89 | Self { 90 | no_setup_logger: false, 91 | default_log_level: "info".to_string(), 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/shim/shim.rs: -------------------------------------------------------------------------------- 1 | use std::hash::Hash; 2 | 3 | use anyhow::Result; 4 | #[doc(inline)] 5 | pub use containerd_shimkit::sandbox::cli::Version; 6 | 7 | use crate::sandbox::Sandbox; 8 | use crate::sandbox::context::WasmLayer; 9 | 10 | /// The `Shim` trait provides a simplified API for running WebAssembly containers. 11 | /// 12 | /// It handles the lifecycle of the container and OCI spec details for you. 13 | #[trait_variant::make(Send)] 14 | pub trait Shim: Sync + 'static { 15 | /// The name to use for this shim 16 | fn name() -> &'static str; 17 | 18 | /// Returns the shim version. 19 | /// Usually implemented using the [`version!()`](crate::shim::version) macro. 20 | fn version() -> Version { 21 | Version::default() 22 | } 23 | 24 | type Sandbox: Sandbox; 25 | 26 | /// When `compiler` returns `Some`, the returned `Compiler` will be used to precompile 27 | /// the layers before they are run. 28 | /// Returns the compiler to be used by this engine 29 | /// to precompile layers. 30 | async fn compiler() -> Option { 31 | async move { NO_COMPILER } 32 | } 33 | 34 | /// Return the supported OCI layer types 35 | /// This is used to filter only layers that are supported by the runtime. 36 | /// The default implementation returns the OCI layer type 'application/vnd.bytecodealliance.wasm.component.layer.v0+wasm' 37 | /// for WASM modules which can be contain with wasip1 or wasip2 components. 38 | /// Runtimes can override this to support other layer types 39 | /// such as lays that contain runtime specific configuration 40 | fn supported_layers_types() -> &'static [&'static str] { 41 | &[ 42 | "application/vnd.bytecodealliance.wasm.component.layer.v0+wasm", 43 | "application/wasm", 44 | ] 45 | } 46 | } 47 | 48 | #[trait_variant::make(Send)] 49 | pub trait Compiler: Sync { 50 | /// `cache_key` returns a hasable type that will be used as a cache key for the precompiled module. 51 | /// 52 | /// the return value should at least include the version of the shim running but could include other information such as 53 | /// a hash of the version and cpu type and other important information in the validation of being able to use precompiled module. 54 | /// If the hash doesn't match then the module will be recompiled and cached with the new cache_key. 55 | /// 56 | /// This hash will be used in the following way: 57 | /// "runwasi.io/precompiled//" 58 | fn cache_key(&self) -> impl Hash; 59 | 60 | /// `compile` passes supported OCI layers to engine for compilation. 61 | /// This is used to precompile the layers before they are run. 62 | /// It is called only the first time a module is run and the resulting bytes will be cached in the containerd content store. 63 | /// The cached, precompiled layers will be reloaded on subsequent runs. 64 | /// The runtime is expected to return the same number of layers passed in, if the layer cannot be precompiled it should return `None` for that layer. 65 | /// In some edge cases it is possible that the layers may already be precompiled and None should be returned in this case. 66 | async fn compile(&self, _layers: &[WasmLayer]) -> Result>>>; 67 | } 68 | 69 | /// Like the unstable never type, this type can never be constructed. 70 | /// Ideally we should use the never type (`!`), but it's unstable. 71 | /// This type can be used to indicate that an engine doesn't support 72 | /// precompilation 73 | #[doc(hidden)] 74 | #[derive(Clone, Copy)] 75 | pub enum NoCompiler {} 76 | 77 | #[doc(hidden)] 78 | pub const NO_COMPILER: Option = None; 79 | 80 | impl Compiler for NoCompiler { 81 | fn cache_key(&self) -> impl Hash { 82 | unreachable!() 83 | } 84 | 85 | async fn compile(&self, _layers: &[WasmLayer]) -> anyhow::Result>>> { 86 | unreachable!() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/shim/tests.rs: -------------------------------------------------------------------------------- 1 | use anyhow::bail; 2 | 3 | use super::shim::Shim; 4 | use crate::sandbox::Sandbox; 5 | use crate::sandbox::context::RuntimeContext; 6 | use crate::testing::WasiTest; 7 | 8 | struct EngineFailingValidation; 9 | 10 | #[derive(Default)] 11 | struct ContainerFailingValidation; 12 | 13 | impl Sandbox for ContainerFailingValidation { 14 | async fn can_handle(&self, _ctx: &impl RuntimeContext) -> anyhow::Result<()> { 15 | bail!("can't handle"); 16 | } 17 | async fn run_wasi(&self, _ctx: &impl RuntimeContext) -> anyhow::Result { 18 | Ok(0) 19 | } 20 | } 21 | 22 | impl Shim for EngineFailingValidation { 23 | fn name() -> &'static str { 24 | "wasi_instance" 25 | } 26 | 27 | type Sandbox = ContainerFailingValidation; 28 | } 29 | 30 | #[test] 31 | #[cfg(unix)] // not yet implemented on Windows 32 | fn test_validation_error() -> anyhow::Result<()> { 33 | // A validation error should fail when creating the container 34 | // as opposed to failing when starting it. 35 | 36 | let result = WasiTest::::builder()? 37 | .with_start_fn("foo") 38 | .with_wasm("/invalid_entrypoint.wasm")? 39 | .build(); 40 | 41 | assert!(result.is_err()); 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/shim/wasm.rs: -------------------------------------------------------------------------------- 1 | use wasmparser::Parser; 2 | 3 | /// The type of a wasm binary. 4 | pub enum WasmBinaryType { 5 | /// A wasm module. 6 | Module, 7 | /// A wasm component. 8 | Component, 9 | } 10 | 11 | impl WasmBinaryType { 12 | /// Returns the type of the wasm binary. 13 | pub fn from_bytes(bytes: &[u8]) -> Option { 14 | if Parser::is_component(bytes) { 15 | Some(Self::Component) 16 | } else if Parser::is_core_wasm(bytes) { 17 | Some(Self::Module) 18 | } else { 19 | None 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/sys/unix/container/container.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::io::Error as IoError; 3 | use std::mem::transmute; 4 | 5 | use anyhow::{Context, anyhow}; 6 | use containerd_shimkit::zygote::{WireError, Zygote}; 7 | use libcontainer::container::Container as YoukiContainer; 8 | use libcontainer::signal::Signal; 9 | use serde::Serialize; 10 | use serde::de::DeserializeOwned; 11 | 12 | thread_local! { 13 | // The youki's Container will live in a static inside the zygote process. 14 | // Reserve some space for it here. 15 | static CONTAINER: RefCell> = RefCell::default(); 16 | } 17 | 18 | // The exposed container is just a wrapper around the zygore process 19 | pub struct Container(Zygote); 20 | 21 | // Constructor methods 22 | impl Container { 23 | pub fn build( 24 | f: fn(Arg) -> anyhow::Result, 25 | arg: Arg, 26 | ) -> anyhow::Result { 27 | let zygote = Zygote::global().spawn(); 28 | let container = Container(zygote); 29 | container.run_init(f, arg)?; 30 | 31 | Ok(container) 32 | } 33 | } 34 | 35 | // Wrap the youki's Container methods that we use 36 | impl Container { 37 | pub fn pid(&self) -> anyhow::Result { 38 | self.run(|c, _| Ok(c.pid().map(|pid| pid.as_raw())), ())? 39 | .context("Failed to obtain PID") 40 | } 41 | 42 | pub fn start(&self) -> anyhow::Result<()> { 43 | self.run(|c, _| Ok(c.start()?), ()) 44 | } 45 | pub fn kill(&self, signal: u32) -> anyhow::Result<()> { 46 | self.run( 47 | |c, signal| { 48 | let signal = Signal::try_from(signal as i32).context("invalid signal number")?; 49 | Ok(c.kill(signal, true)?) 50 | }, 51 | signal, 52 | ) 53 | } 54 | pub fn delete(&self) -> anyhow::Result<()> { 55 | self.run(|c, _| Ok(c.delete(true)?), ()) 56 | } 57 | } 58 | 59 | impl Container { 60 | fn run_impl< 61 | Arg: Serialize + DeserializeOwned + 'static, 62 | T: Serialize + DeserializeOwned + 'static, 63 | >( 64 | &self, 65 | f: fn(&mut Option, Arg) -> anyhow::Result, 66 | arg: Arg, 67 | ) -> anyhow::Result { 68 | self.0 69 | .run( 70 | |(f, arg)| { 71 | let f: fn(&mut Option, Arg) -> anyhow::Result = 72 | unsafe { transmute(f) }; 73 | CONTAINER.with_borrow_mut(|c| -> Result { 74 | Ok(f(c, arg).map_err(IoError::other)?) 75 | }) 76 | }, 77 | (f as usize, arg), 78 | ) 79 | .map_err(|e| anyhow!(e)) 80 | } 81 | 82 | fn run_init( 83 | &self, 84 | f: fn(Arg) -> anyhow::Result, 85 | arg: Arg, 86 | ) -> anyhow::Result<()> { 87 | self.run_impl( 88 | |c: &mut Option, (f, arg): (usize, Arg)| -> anyhow::Result<()> { 89 | let f: fn(Arg) -> anyhow::Result = unsafe { transmute(f) }; 90 | *c = Some(f(arg)?); 91 | Ok(()) 92 | }, 93 | (f as usize, arg), 94 | ) 95 | } 96 | 97 | fn run< 98 | Arg: Serialize + DeserializeOwned + 'static, 99 | T: Serialize + DeserializeOwned + 'static, 100 | >( 101 | &self, 102 | f: fn(&mut YoukiContainer, Arg) -> anyhow::Result, 103 | arg: Arg, 104 | ) -> anyhow::Result { 105 | self.run_impl( 106 | |c: &mut Option, (f, arg): (usize, Arg)| -> anyhow::Result { 107 | let f: fn(&mut YoukiContainer, Arg) -> anyhow::Result = unsafe { transmute(f) }; 108 | let c = c.as_mut().expect("Container not initialized"); 109 | f(c, arg) 110 | }, 111 | (f as usize, arg), 112 | ) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/sys/unix/container/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(clippy::module_inception)] 2 | mod container; 3 | 4 | mod executor; 5 | pub mod instance; 6 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/sys/unix/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod container; 2 | 3 | mod pid_fd; 4 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/sys/unix/pid_fd.rs: -------------------------------------------------------------------------------- 1 | use std::os::fd::{AsFd, FromRawFd as _, OwnedFd, RawFd}; 2 | use std::time::Duration; 3 | 4 | use containerd_shim::monitor::{ExitEvent, Subject, Subscription, Topic, monitor_subscribe}; 5 | use libc::pid_t; 6 | use nix::errno::Errno; 7 | use nix::sys::wait::{Id, WaitPidFlag, WaitStatus, waitid}; 8 | use nix::unistd::Pid; 9 | use tokio::io::unix::AsyncFd; 10 | 11 | pub(super) struct PidFd { 12 | fd: OwnedFd, 13 | pid: pid_t, 14 | subs: Subscription, 15 | } 16 | 17 | impl PidFd { 18 | pub(super) fn new(pid: impl Into) -> anyhow::Result { 19 | use libc::{PIDFD_NONBLOCK, SYS_pidfd_open, syscall}; 20 | let pid = pid.into(); 21 | let subs = monitor_subscribe(Topic::Pid)?; 22 | let pidfd = unsafe { syscall(SYS_pidfd_open, pid, PIDFD_NONBLOCK) }; 23 | if pidfd == -1 { 24 | return Err(std::io::Error::last_os_error().into()); 25 | } 26 | let fd = unsafe { OwnedFd::from_raw_fd(pidfd as RawFd) }; 27 | Ok(Self { fd, pid, subs }) 28 | } 29 | 30 | pub(super) async fn wait(self) -> std::io::Result { 31 | let fd = AsyncFd::new(self.fd)?; 32 | loop { 33 | // Check with non-blocking waitid before awaiting on fd. 34 | // On some platforms, the readiness detecting mechanism relies on 35 | // edge-triggered notifications. 36 | // This means that we could miss a notification if the process exits 37 | // before we create the AsyncFd. 38 | // See https://docs.rs/tokio/latest/tokio/io/unix/struct.AsyncFd.html 39 | match waitid( 40 | Id::PIDFd(fd.as_fd()), 41 | WaitPidFlag::WEXITED | WaitPidFlag::WNOHANG, 42 | ) { 43 | Ok(WaitStatus::StillAlive) => { 44 | let _ = fd.readable().await?; 45 | } 46 | Ok(status) => { 47 | return Ok(status); 48 | } 49 | Err(Errno::ECHILD) => { 50 | // The process has already been reaped by the containerd-shim reaper. 51 | // Get the status from there. 52 | let status = try_wait_pid(self.pid, self.subs).await?; 53 | return Ok(WaitStatus::Exited(Pid::from_raw(self.pid), status)); 54 | } 55 | Err(err) => return Err(err.into()), 56 | } 57 | } 58 | } 59 | } 60 | 61 | pub async fn try_wait_pid(pid: i32, s: Subscription) -> Result { 62 | tokio::task::spawn_blocking(move || { 63 | while let Ok(ExitEvent { subject, exit_code }) = s.rx.recv_timeout(Duration::from_secs(2)) { 64 | let Subject::Pid(p) = subject else { 65 | continue; 66 | }; 67 | if pid == p { 68 | return Ok(exit_code); 69 | } 70 | } 71 | Err(Errno::ECHILD) 72 | }) 73 | .await 74 | .map_err(|_| Errno::ECHILD)? 75 | } 76 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/sys/windows/container/instance.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use chrono::{DateTime, Utc}; 4 | use containerd_shimkit::sandbox::{ 5 | Error as SandboxError, Instance as SandboxInstance, InstanceConfig, 6 | }; 7 | 8 | use crate::shim::Shim; 9 | 10 | pub struct Instance(PhantomData); 11 | 12 | impl SandboxInstance for Instance { 13 | async fn new(_id: String, _cfg: &InstanceConfig) -> Result { 14 | todo!(); 15 | } 16 | 17 | /// Start the instance 18 | /// The returned value should be a unique ID (such as a PID) for the instance. 19 | /// Nothing internally should be using this ID, but it is returned to containerd where a user may want to use it. 20 | async fn start(&self) -> Result { 21 | todo!(); 22 | } 23 | 24 | /// Send a signal to the instance 25 | async fn kill(&self, _signal: u32) -> Result<(), SandboxError> { 26 | todo!(); 27 | } 28 | 29 | /// Delete any reference to the instance 30 | /// This is called after the instance has exited. 31 | async fn delete(&self) -> Result<(), SandboxError> { 32 | todo!(); 33 | } 34 | 35 | /// Waits for the instance to finish and returns its exit code 36 | /// Returns None if the timeout is reached before the instance has finished. 37 | /// This is an async call. 38 | async fn wait(&self) -> (u32, DateTime) { 39 | todo!(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/sys/windows/container/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod instance; 2 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/sys/windows/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod container; 2 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasm/src/test/mod.rs: -------------------------------------------------------------------------------- 1 | mod signals; 2 | 3 | #[ctor::ctor] 4 | fn init_zygote() { 5 | containerd_shimkit::zygote::Zygote::global(); 6 | } 7 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmedge/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "containerd-shim-wasmedge" 3 | version = "0.6.0" 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | anyhow = { workspace = true } 8 | containerd-shim-wasm = { workspace = true, features = ["opentelemetry"] } 9 | log = { workspace = true } 10 | cfg-if = { workspace = true } 11 | 12 | # may need to bump wasmedge version in scripts/setup-windows.sh 13 | wasmedge-sdk = { version = "0.14.0", default-features = false } 14 | 15 | [dev-dependencies] 16 | containerd-shim-wasm = { workspace = true, features = ["testing"] } 17 | libc = { workspace = true } 18 | serial_test = { workspace = true } 19 | 20 | [features] 21 | default = ["standalone", "static", "plugin"] 22 | standalone = ["wasmedge-sdk/standalone"] 23 | static = ["wasmedge-sdk/static"] 24 | plugin = ["wasmedge-sdk/wasi_nn"] 25 | 26 | [[bin]] 27 | name = "containerd-shim-wasmedge-v1" 28 | path = "src/main.rs" 29 | 30 | [package.metadata.cargo-machete] 31 | ignored = ["log"] 32 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmedge/src/instance.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::env; 3 | #[cfg(all(feature = "plugin", not(target_env = "musl")))] 4 | use std::str::FromStr; 5 | 6 | use anyhow::{Context, Result}; 7 | use cfg_if::cfg_if; 8 | use containerd_shim_wasm::sandbox::Sandbox; 9 | use containerd_shim_wasm::sandbox::context::{Entrypoint, RuntimeContext}; 10 | use containerd_shim_wasm::shim::{Shim, Version, version}; 11 | #[cfg(all(feature = "plugin", not(target_env = "musl")))] 12 | use wasmedge_sdk::AsInstance; 13 | use wasmedge_sdk::config::{CommonConfigOptions, Config, ConfigBuilder}; 14 | #[cfg(all(feature = "plugin", not(target_env = "musl")))] 15 | use wasmedge_sdk::plugin::NNPreload; 16 | #[cfg(all(feature = "plugin", not(target_env = "musl")))] 17 | use wasmedge_sdk::plugin::PluginManager; 18 | use wasmedge_sdk::wasi::WasiModule; 19 | use wasmedge_sdk::{Module, Store, Vm}; 20 | 21 | pub struct WasmEdgeShim; 22 | 23 | pub struct WasmEdgeSandbox { 24 | config: Config, 25 | } 26 | 27 | impl Default for WasmEdgeSandbox { 28 | fn default() -> Self { 29 | let config = ConfigBuilder::new(CommonConfigOptions::default()) 30 | .build() 31 | .expect("failed to create config"); 32 | Self { config } 33 | } 34 | } 35 | 36 | impl Shim for WasmEdgeShim { 37 | fn name() -> &'static str { 38 | "wasmedge" 39 | } 40 | 41 | fn version() -> Version { 42 | version!() 43 | } 44 | 45 | type Sandbox = WasmEdgeSandbox; 46 | } 47 | 48 | impl Sandbox for WasmEdgeSandbox { 49 | async fn run_wasi(&self, ctx: &impl RuntimeContext) -> Result { 50 | let args = ctx.args(); 51 | let envs = ctx.envs(); 52 | let Entrypoint { 53 | source, 54 | func, 55 | arg0: _, 56 | name, 57 | } = ctx.entrypoint(); 58 | 59 | log::debug!("initializing WasmEdge runtime"); 60 | 61 | let prefix = "WASMEDGE_"; 62 | for env in envs.iter().filter(|env| env.starts_with(prefix)) { 63 | if let Some((key, value)) = env.split_once('=') { 64 | unsafe { 65 | env::set_var(key, value); 66 | } 67 | } 68 | } 69 | 70 | let mut instances = HashMap::new(); 71 | cfg_if! { 72 | if #[cfg(all(feature = "plugin", not(target_env = "musl")))] { 73 | PluginManager::load(None)?; 74 | match env::var("WASMEDGE_WASINN_PRELOAD") { 75 | Ok(value) => PluginManager::nn_preload(vec![NNPreload::from_str(value.as_str())?]), 76 | Err(_) => log::debug!("No specific nn_preload parameter for wasi_nn plugin"), 77 | } 78 | 79 | // Load the wasi_nn plugin manually as a workaround. 80 | // It should call auto_detect_plugins after the issue is fixed. 81 | let mut wasi_nn = PluginManager::names() 82 | .contains(&"wasi_nn".to_string()) 83 | .then(PluginManager::load_plugin_wasi_nn) 84 | .transpose()?; 85 | if let Some(ref mut nn) = wasi_nn { 86 | instances.insert(nn.name().unwrap().to_string(), nn); 87 | } 88 | } 89 | } 90 | 91 | let mut wasi_module = WasiModule::create( 92 | Some(args.iter().map(String::as_str).collect()), 93 | Some(envs.iter().map(String::as_str).collect()), 94 | Some(vec!["/:/"]), 95 | )?; 96 | instances.insert(wasi_module.name().to_string(), wasi_module.as_mut()); 97 | 98 | let wasm_bytes = source.as_bytes()?; 99 | let module = Module::from_bytes(Some(&self.config), &wasm_bytes)?; 100 | let mut vm = Vm::new(Store::new(Some(&self.config), instances).unwrap()); 101 | let mod_name = name.unwrap_or_else(|| "main".to_string()); 102 | 103 | let vm = vm 104 | .register_module(Some(&mod_name), module) 105 | .context("registering module")?; 106 | 107 | log::debug!("running with method {func:?}"); 108 | vm.run_func(Some(&mod_name), func, vec![])?; 109 | 110 | Ok(wasi_module.exit_code() as i32) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmedge/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod instance; 2 | 3 | pub use instance::WasmEdgeShim; 4 | 5 | #[cfg(unix)] 6 | #[cfg(test)] 7 | #[path = "tests.rs"] 8 | mod wasmedge_tests; 9 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmedge/src/main.rs: -------------------------------------------------------------------------------- 1 | use containerd_shim_wasm::shim::Cli; 2 | use containerd_shim_wasmedge::WasmEdgeShim; 3 | 4 | fn main() { 5 | WasmEdgeShim::run(None); 6 | } 7 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "containerd-shim-wasmer" 3 | version = "0.6.0" 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | anyhow = { workspace = true } 8 | containerd-shim-wasm = { workspace = true, features = ["opentelemetry"] } 9 | log = { workspace = true } 10 | tokio = { workspace = true } 11 | 12 | wasmer = "5.0.2" 13 | wasmer-wasix = "0.32" 14 | mio = { version = "1", features = ["net"] } 15 | 16 | [dev-dependencies] 17 | containerd-shim-wasm = { workspace = true, features = ["testing"] } 18 | serial_test = { workspace = true } 19 | 20 | [[bin]] 21 | name = "containerd-shim-wasmer-v1" 22 | path = "src/main.rs" 23 | 24 | [package.metadata.cargo-machete] 25 | ignored = ["mio", "log"] # wasmer depends on mio but the latest version of mio moved features behind a feature flag 26 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmer/src/instance.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use containerd_shim_wasm::sandbox::Sandbox; 3 | use containerd_shim_wasm::sandbox::context::{Entrypoint, RuntimeContext}; 4 | use containerd_shim_wasm::shim::{Shim, Version, version}; 5 | use tokio::runtime::Handle; 6 | use wasmer::{Module, Store}; 7 | use wasmer_wasix::virtual_fs::host_fs::FileSystem; 8 | use wasmer_wasix::{WasiEnv, WasiError}; 9 | 10 | pub struct WasmerShim; 11 | 12 | #[derive(Default)] 13 | pub struct WasmerSandbox { 14 | engine: wasmer::Cranelift, 15 | } 16 | 17 | impl Shim for WasmerShim { 18 | fn name() -> &'static str { 19 | "wasmer" 20 | } 21 | 22 | fn version() -> Version { 23 | version!() 24 | } 25 | 26 | type Sandbox = WasmerSandbox; 27 | } 28 | 29 | impl Sandbox for WasmerSandbox { 30 | async fn run_wasi(&self, ctx: &impl RuntimeContext) -> Result { 31 | let args = ctx.args(); 32 | let envs = ctx 33 | .envs() 34 | .iter() 35 | .map(|v| match v.split_once('=') { 36 | None => (v.to_string(), String::new()), 37 | Some((key, value)) => (key.to_string(), value.to_string()), 38 | }) 39 | .collect::>(); 40 | let Entrypoint { 41 | source, 42 | func, 43 | arg0: _, 44 | name, 45 | } = ctx.entrypoint(); 46 | 47 | let mod_name = name.unwrap_or_else(|| "main".to_string()); 48 | 49 | log::info!("Create a Store"); 50 | let mut store = Store::new(self.engine.clone()); 51 | 52 | let wasm_bytes = source.as_bytes()?; 53 | let module = Module::from_binary(&store, &wasm_bytes)?; 54 | 55 | log::info!("Creating `WasiEnv`...: args {args:?}, envs: {envs:?}"); 56 | let fs = FileSystem::new(Handle::current(), "/")?; 57 | let (instance, wasi_env) = WasiEnv::builder(mod_name) 58 | .args(&args[1..]) 59 | .envs(envs) 60 | .fs(Box::new(fs)) 61 | .preopen_dir("/")? 62 | .instantiate(module, &mut store)?; 63 | 64 | log::info!("Running {func:?}"); 65 | let start = instance.exports.get_function(&func)?; 66 | wasi_env.data(&store).thread.set_status_running(); 67 | let status = tokio::task::block_in_place(|| { 68 | start.call(&mut store, &[]).map(|_| 0).or_else(|err| { 69 | match err.downcast_ref::() { 70 | Some(WasiError::Exit(code)) => Ok(code.raw()), 71 | _ => Err(err), 72 | } 73 | }) 74 | })?; 75 | 76 | Ok(status) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmer/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod instance; 2 | 3 | pub use instance::WasmerShim; 4 | 5 | #[cfg(unix)] 6 | #[cfg(test)] 7 | #[path = "tests.rs"] 8 | mod wasmer_tests; 9 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmer/src/main.rs: -------------------------------------------------------------------------------- 1 | use containerd_shim_wasm::shim::Cli; 2 | use containerd_shim_wasmer::WasmerShim; 3 | 4 | fn main() { 5 | WasmerShim::run(None); 6 | } 7 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmer/src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use containerd_shim_wasm::testing::WasiTest; 4 | //use containerd_shim_wasm::sandbox::Instance; 5 | use containerd_shim_wasm::testing::modules::*; 6 | use serial_test::serial; 7 | 8 | use crate::WasmerShim as WasiEngine; 9 | 10 | #[test] 11 | #[serial] 12 | fn test_delete_after_create() -> anyhow::Result<()> { 13 | WasiTest::::builder()?.build()?.delete()?; 14 | Ok(()) 15 | } 16 | 17 | #[test] 18 | #[serial] 19 | fn test_hello_world() -> anyhow::Result<()> { 20 | let (exit_code, stdout, _) = WasiTest::::builder()? 21 | .with_wasm(HELLO_WORLD)? 22 | .build()? 23 | .start()? 24 | .wait(Duration::from_secs(10))?; 25 | 26 | assert_eq!(exit_code, 0); 27 | assert_eq!(stdout, "hello world\n"); 28 | 29 | Ok(()) 30 | } 31 | 32 | #[test] 33 | #[serial] 34 | fn test_hello_world_oci() -> anyhow::Result<()> { 35 | let (builder, _oci_cleanup) = WasiTest::::builder()? 36 | .with_wasm(HELLO_WORLD)? 37 | .as_oci_image(None, None)?; 38 | 39 | let (exit_code, stdout, _) = builder.build()?.start()?.wait(Duration::from_secs(10))?; 40 | 41 | assert_eq!(exit_code, 0); 42 | assert_eq!(stdout, "hello world\n"); 43 | 44 | Ok(()) 45 | } 46 | 47 | #[test] 48 | #[serial] 49 | fn test_custom_entrypoint() -> anyhow::Result<()> { 50 | let (exit_code, stdout, _) = WasiTest::::builder()? 51 | .with_start_fn("foo") 52 | .with_wasm(CUSTOM_ENTRYPOINT)? 53 | .build()? 54 | .start()? 55 | .wait(Duration::from_secs(10))?; 56 | 57 | assert_eq!(exit_code, 0); 58 | assert_eq!(stdout, "hello world\n"); 59 | 60 | Ok(()) 61 | } 62 | 63 | #[test] 64 | #[serial] 65 | fn test_unreachable() -> anyhow::Result<()> { 66 | let (exit_code, _, _) = WasiTest::::builder()? 67 | .with_wasm(UNREACHABLE)? 68 | .build()? 69 | .start()? 70 | .wait(Duration::from_secs(10))?; 71 | 72 | assert_ne!(exit_code, 0); 73 | 74 | Ok(()) 75 | } 76 | 77 | #[test] 78 | #[serial] 79 | fn test_exit_code() -> anyhow::Result<()> { 80 | let (exit_code, _, _) = WasiTest::::builder()? 81 | .with_wasm(EXIT_CODE)? 82 | .build()? 83 | .start()? 84 | .wait(Duration::from_secs(10))?; 85 | 86 | assert_eq!(exit_code, 42); 87 | 88 | Ok(()) 89 | } 90 | 91 | #[test] 92 | #[serial] 93 | fn test_seccomp() -> anyhow::Result<()> { 94 | let (exit_code, stdout, _) = WasiTest::::builder()? 95 | .with_wasm(SECCOMP)? 96 | .build()? 97 | .start()? 98 | .wait(Duration::from_secs(10))?; 99 | 100 | assert_eq!(exit_code, 0); 101 | assert_eq!(stdout.trim(), "current working dir: /"); 102 | 103 | Ok(()) 104 | } 105 | 106 | #[test] 107 | #[serial] 108 | fn test_has_default_devices() -> anyhow::Result<()> { 109 | let (exit_code, _, _) = WasiTest::::builder()? 110 | .with_wasm(HAS_DEFAULT_DEVICES)? 111 | .build()? 112 | .start()? 113 | .wait(Duration::from_secs(10))?; 114 | 115 | assert_eq!(exit_code, 0); 116 | 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "containerd-shim-wasmtime" 3 | version = "0.6.0" 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | anyhow = { workspace = true } 8 | containerd-shim-wasm = { workspace = true, features = ["opentelemetry"] } 9 | libc = { workspace = true } 10 | log = { workspace = true } 11 | hyper = { workspace = true } 12 | tokio = { workspace = true, features = ["signal", "macros"] } 13 | tokio-util = { workspace = true, features = ["rt"] } 14 | 15 | wasmtime = { workspace = true } 16 | wasmtime-wasi = { workspace = true } 17 | wasmtime-wasi-http = { workspace = true } 18 | 19 | [dev-dependencies] 20 | containerd-shim-wasm = { workspace = true, features = ["testing"] } 21 | serial_test = { workspace = true } 22 | reqwest = { version = "0.12", default-features=false, features = ["blocking"] } 23 | 24 | [[bin]] 25 | name = "containerd-shim-wasmtime-v1" 26 | path = "src/main.rs" 27 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmtime/README.md: -------------------------------------------------------------------------------- 1 | ## containerd-shim-wasmtime 2 | 3 | This is a [containerd] shim for running WebAssembly modules and components using [wasmtime]. 4 | 5 | [containerd]: https://containerd.io/ 6 | [wasmtime]: https://wasmtime.dev/ 7 | 8 | ### WASI 9 | 10 | This shim assumes that the Wasm modules it runs are [WASI] modules. WASI is a system interface for WebAssembly. If no 11 | entrypoint is specified, the shim will look for a `_start` function in the module, which is an initial point of 12 | execution when the module is loaded in the runtime. The `_start` function is a WASI convention for the Command modules 13 | (see the [distinction between the Command and Reactors](https://github.com/WebAssembly/WASI/issues/13)). 14 | 15 | The shim adds experimental support for running [WASI 0.2](https://wasi.dev/interfaces#wasi-02) Wasm components. 16 | If no entrypoint is specified, the shim will assume that the WASI component is a component that uses the [wasi:cli/command](https://github.com/WebAssembly/wasi-cli) world. 17 | 18 | 19 | ### WASI/HTTP 20 | 21 | The `wasmtime-shim` supports [`wasi/http`][1] and can be used to serve requests from a `wasi/http` proxy component. The 22 | shim code will try to detect components targeting `http/proxy`, and start up a hyper server to listen for incoming 23 | connections, and forward the incoming requests to the WASM component for handling. 24 | 25 | This behavior is very similar to what the [`wasmtime serve`][2] command currently offers. The server task is terminated 26 | upon receiving a terminate or interrupt signal in the container. 27 | 28 | This can be very useful on the Wasm-first platforms to allow instance-per-request isolation: 29 | 30 | > Eeach Wasm instance serves only one HTTP request, and then goes away. This is fantastic for security and bug 31 | > mitigation: the blast radius of an exploit or guest-runtime bug is only a single request, and can never see the data 32 | > from other users of the platform or even other requests by the same user. [3] 33 | 34 | The server can be customized by setting environment variables passed to the `RuntimeContext`. These variables include: 35 | 36 | - `WASMTIME_HTTP_PROXY_SOCKET_ADDR`: Defines the socket address to bind to 37 | (default: 0.0.0.0:8080). 38 | - `WASMTIME_HTTP_PROXY_BACKLOG`: Defines the maximum number of pending 39 | connections in the queue (default: 100). 40 | 41 | #### Getting Started 42 | First, we need to create a Wasm component that uses `http/proxy`. You can follow the instructions in this [article][4] 43 | to develop a Wasm application using `cargo-component`. 44 | 45 | Alternatively, you can use the pre-built [Hello World][5] example from this repository, which responds with 46 | "Hello World" text for GET requests. 47 | 48 | - Create an OCI Image 49 | 50 | Use `oci-tar-builder` to create an OCI image with our `http-handler`. Assuming our Wasm component is named `wasi-http.wasm`: 51 | 52 | ```shell 53 | cargo run --bin oci-tar-builder -- \ 54 | --name wasi-demo-http \ 55 | --repo ghcr.io/containerd/runwasi \ 56 | --tag latest --module wasi-http.wasm \ 57 | -o ./dist/wasi-http-img-oci.tar 58 | ``` 59 | 60 | - Pull the image: 61 | 62 | ```shell 63 | make pull-http 64 | ``` 65 | 66 | - Run the image: 67 | 68 | ```shell 69 | sudo ctr run --rm --net-host --runtime=io.containerd.wasmtime.v1 \ 70 | ghcr.io/containerd/runwasi/wasi-demo-http:latest wasi-http /wasi-http.wasm 71 | ``` 72 | 73 | - Finally, assuming our handler will respond to `GET` requests at `/`, we can 74 | use `curl` to send a request: 75 | 76 | ```shell 77 | curl 127.0.0.1:8080 78 | Hello, this is your first wasi:http/proxy world! 79 | ``` 80 | 81 | [WASI]: https://wasi.dev/ 82 | [1]: https://github.com/WebAssembly/wasi-http 83 | [2]: https://docs.wasmtime.dev/cli-options.html#serve 84 | [3]: https://cfallin.org/blog/2024/08/27/aot-js/ 85 | [4]: https://opensource.microsoft.com/blog/2024/09/25/distributing-webassembly-components-using-oci-registries/ 86 | [5]: ../containerd-shim-wasm-test-modules/src/modules//component-hello-world.wasm 87 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod http_proxy; 2 | pub mod instance; 3 | 4 | pub use instance::WasmtimeShim; 5 | 6 | #[cfg(unix)] 7 | #[cfg(test)] 8 | #[path = "tests.rs"] 9 | mod wasmtime_tests; 10 | -------------------------------------------------------------------------------- /crates/containerd-shim-wasmtime/src/main.rs: -------------------------------------------------------------------------------- 1 | use containerd_shim_wasm::shim::Cli; 2 | use containerd_shim_wasmtime::WasmtimeShim; 3 | 4 | fn main() { 5 | WasmtimeShim::run(None); 6 | } 7 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## [Unreleased] 6 | 7 | ## [v0.1.1] - 2025-03-27 8 | 9 | ### Added 10 | 11 | - Added `containerd_shimkit::set_logger_kv` to allow for setting logger key values. This is useful for passing the container ID and pod ID to the shim. 12 | 13 | ## [v0.1.0] - 2025-03-26 14 | 15 | ### Added 16 | - Moved lower level (non wasi/wasm specific) APIs from `containerd-shim-wasm` to `containerd-shimkit`. ([#930](https://github.com/containerd/runwasi/pull/930)) 17 | 18 | [Unreleased]: 19 | [v0.1.1]: 20 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "containerd-shimkit" 3 | description = "Opinionated library for building containerd shims" 4 | version = "0.1.1" 5 | edition.workspace = true 6 | license.workspace = true 7 | readme = "README.md" 8 | homepage.workspace = true 9 | repository.workspace = true 10 | 11 | [lib] 12 | doctest = false 13 | 14 | [dependencies] 15 | anyhow = { workspace = true } 16 | chrono = { workspace = true } 17 | containerd-shim = { workspace = true } 18 | git-version = { version = "0.3.9" } 19 | log = { workspace = true } 20 | oci-spec = { workspace = true } 21 | protobuf = { workspace = true } 22 | serde = { workspace = true } 23 | serde_json = { workspace = true } 24 | tempfile = { workspace = true, optional = true } 25 | thiserror = { workspace = true } 26 | tokio = { workspace = true, features = ["rt-multi-thread", "macros", "sync", "time"] } 27 | futures = { version = "0.3.30" } 28 | serde_bytes = "0.11" 29 | prost = "0.13" 30 | toml = "0.8" 31 | trait-variant = "0.1" 32 | tokio-async-drop = "0.1" 33 | 34 | # tracing 35 | # note: it's important to keep the version of tracing in sync with tracing-subscriber 36 | tracing = { workspace = true, optional = true } 37 | # does not include `tracing-log` feature due to https://github.com/spinkube/containerd-shim-spin/issues/61 38 | tracing-subscriber = { version = "0.3", default-features = false, features = [ 39 | "smallvec", # Enables performance optimizations 40 | "fmt", 41 | "ansi", 42 | "std", 43 | "env-filter", 44 | "json", 45 | "registry", 46 | ], optional = true } 47 | 48 | # opentelemetry 49 | opentelemetry = { version = "0.23", features = ["trace"], optional = true, default-features = false} 50 | opentelemetry-otlp = { version = "0.16.0", default-features = false, features = [ 51 | "grpc-tonic", 52 | "http-proto", 53 | "reqwest-client", 54 | "trace", 55 | ], optional = true } 56 | opentelemetry_sdk = { version = "0.23", default-features = false, features = [ 57 | "rt-tokio", 58 | ], optional = true } 59 | tracing-opentelemetry = { version = "0.24", optional = true } 60 | 61 | # vendored code 62 | time = { version = "0.3.41", features = ["serde", "std", "formatting"] } 63 | 64 | [target.'cfg(unix)'.dependencies] 65 | zygote = { version = "0.1.2" } 66 | caps = "0.5" 67 | # this must match the version pulled by libcontainer 68 | dbus = { version = "0", features = ["vendored"] } 69 | libcontainer = { workspace = true, features = [ 70 | "libseccomp", 71 | "systemd", 72 | "v1", 73 | "v2", 74 | ] } 75 | nix = { workspace = true, features = ["sched", "mount"] } 76 | containerd-client = "0.6.0" 77 | 78 | [target.'cfg(windows)'.dependencies] 79 | windows-sys = { workspace = true, features = [ 80 | "Win32_Foundation", 81 | "Win32_Storage_FileSystem", 82 | ] } 83 | # vendored code 84 | mio = { version = "1.0", features = ["os-ext", "os-poll"] } 85 | 86 | [build-dependencies] 87 | ttrpc-codegen = { version = "0.4.2" } 88 | 89 | [dev-dependencies] 90 | tokio = { workspace = true, features = ["signal"] } 91 | containerd-shim-wasm-test-modules = { workspace = true } 92 | tempfile = { workspace = true } 93 | oci-tar-builder = { workspace = true } 94 | rand = "0.9" 95 | temp-env = "0.3" 96 | 97 | [features] 98 | opentelemetry = [ 99 | "tracing", 100 | "dep:opentelemetry", 101 | "dep:opentelemetry-otlp", 102 | "dep:opentelemetry_sdk", 103 | "dep:tracing-opentelemetry", 104 | ] 105 | tracing = ["dep:tracing", "dep:tracing-subscriber"] 106 | 107 | [package.metadata.cargo-machete] 108 | # used as part of a derive macro 109 | ignored = ["serde_bytes"] -------------------------------------------------------------------------------- /crates/containerd-shimkit/README.md: -------------------------------------------------------------------------------- 1 | # containerd-shimkit 2 | 3 | containerd-shimkit is an opinionated library providing a high-level API for building containerd shims. 4 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | pub mod sandbox; 4 | mod vendor; 5 | 6 | #[cfg_attr(unix, path = "sys/unix/mod.rs")] 7 | #[cfg_attr(windows, path = "sys/windows/mod.rs")] 8 | pub(crate) mod sys; 9 | 10 | pub use containerd_shim::Config; 11 | pub use sandbox::async_utils::AmbientRuntime; 12 | pub use vendor::containerd_shim::logger::set_logger_kv; 13 | #[cfg(unix)] 14 | pub use zygote; 15 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sandbox/async_utils.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(windows, allow(dead_code))] // this is currently used only for linux 2 | 3 | use std::future::Future; 4 | use std::sync::LazyLock; 5 | use std::time::Duration; 6 | 7 | use private::Sealded; 8 | use tokio::task::JoinHandle; 9 | use tokio::time::timeout; 10 | 11 | // A thread local runtime that can be used to run futures to completion. 12 | // It is a current_thread runtime so that it doesn't spawn new threads. 13 | // It is thread local as different threads might want to run futures concurrently. 14 | static RUNTIME: LazyLock = LazyLock::new(|| { 15 | tokio::runtime::Builder::new_multi_thread() 16 | .enable_all() 17 | .build() 18 | .unwrap() 19 | }); 20 | 21 | mod private { 22 | pub trait Sealded {} 23 | impl Sealded for F {} 24 | } 25 | 26 | pub trait AmbientRuntime: Future + Sealded { 27 | fn block_on(self) -> Self::Output 28 | where 29 | Self: Sized, 30 | { 31 | RUNTIME.block_on(self) 32 | } 33 | 34 | #[allow(dead_code, async_fn_in_trait)] // used in tests and with the testing feature 35 | async fn with_timeout(self, t: Duration) -> Option 36 | where 37 | Self: Sized, 38 | { 39 | timeout(t, self).await.ok() 40 | } 41 | 42 | fn spawn(self) -> JoinHandle 43 | where 44 | Self: Sized + Send + 'static, 45 | Self::Output: Send + 'static, 46 | { 47 | RUNTIME.spawn(self) 48 | } 49 | } 50 | 51 | impl AmbientRuntime for F {} 52 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sandbox/instance.rs: -------------------------------------------------------------------------------- 1 | //! Abstractions for running/managing a wasm/wasi instance. 2 | 3 | use std::path::PathBuf; 4 | 5 | use chrono::{DateTime, Utc}; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use super::error::Error; 9 | use crate::sandbox::shim::Config; 10 | 11 | /// Generic options builder for creating a wasm instance. 12 | /// This is passed to the `Instance::new` method. 13 | #[derive(Clone, Serialize, Deserialize, Debug, Default)] 14 | pub struct InstanceConfig { 15 | /// Optional stdin named pipe path. 16 | pub stdin: PathBuf, 17 | /// Optional stdout named pipe path. 18 | pub stdout: PathBuf, 19 | /// Optional stderr named pipe path. 20 | pub stderr: PathBuf, 21 | /// Path to the OCI bundle directory. 22 | pub bundle: PathBuf, 23 | /// Namespace for containerd 24 | pub namespace: String, 25 | /// GRPC address back to main containerd 26 | pub containerd_address: String, 27 | /// containerd runtime options config 28 | pub config: Config, 29 | } 30 | 31 | /// Represents a WASI module(s). 32 | /// Instance is a trait that gets implemented by consumers of this library. 33 | /// This trait requires that any type implementing it is `'static`, similar to `std::any::Any`. 34 | /// This means that the type cannot contain a non-`'static` reference. 35 | #[cfg_attr(not(doc), trait_variant::make(Send))] 36 | pub trait Instance: 'static { 37 | /// Create a new instance 38 | async fn new(id: String, cfg: &InstanceConfig) -> Result 39 | where 40 | Self: Sized; 41 | 42 | /// Start the instance 43 | /// The returned value should be a unique ID (such as a PID) for the instance. 44 | /// Nothing internally should be using this ID, but it is returned to containerd where a user may want to use it. 45 | async fn start(&self) -> Result; 46 | 47 | /// Send a signal to the instance 48 | async fn kill(&self, signal: u32) -> Result<(), Error>; 49 | 50 | /// Delete any reference to the instance 51 | /// This is called after the instance has exited. 52 | async fn delete(&self) -> Result<(), Error>; 53 | 54 | /// Waits for the instance to finish and returns its exit code 55 | /// This is an async call. 56 | async fn wait(&self) -> (u32, DateTime); 57 | } 58 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sandbox/instance_utils.rs: -------------------------------------------------------------------------------- 1 | //! Common utilities for the containerd shims. 2 | 3 | use std::fs::File; 4 | use std::io::{Error as IoError, ErrorKind, Result as IoResult}; 5 | use std::path::PathBuf; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | 9 | use super::{Error, InstanceConfig}; 10 | use crate::sys::DEFAULT_CONTAINER_ROOT_DIR; 11 | use crate::sys::stdio::open; 12 | 13 | #[derive(Serialize, Deserialize)] 14 | struct Options { 15 | root: Option, 16 | } 17 | 18 | impl InstanceConfig { 19 | /// Determine the root directory for the container runtime. 20 | /// 21 | /// If the `bundle` directory contains an `options.json` file, the root directory is read from the 22 | /// file. Otherwise, the root directory is determined by `{DEFAULT_CONTAINER_ROOT_DIR}/{runtime}/{namespace}`. 23 | /// 24 | /// The default root directory is `/run/containerd//`. 25 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Debug"))] 26 | pub fn determine_rootdir( 27 | &self, 28 | runtime: impl AsRef + std::fmt::Debug, 29 | ) -> Result { 30 | let rootdir = DEFAULT_CONTAINER_ROOT_DIR.join(runtime.as_ref()); 31 | let file = match File::open(self.bundle.join("options.json")) { 32 | Ok(f) => f, 33 | Err(e) if e.kind() == ErrorKind::NotFound => return Ok(rootdir.join(&self.namespace)), 34 | Err(e) => return Err(e.into()), 35 | }; 36 | let path = serde_json::from_reader::<_, Options>(file)? 37 | .root 38 | .unwrap_or(rootdir) 39 | .join(&self.namespace); 40 | log::info!("container runtime root path is {path:?}"); 41 | Ok(path) 42 | } 43 | 44 | pub fn open_stdin(&self) -> IoResult { 45 | if self.stdin.as_os_str().is_empty() { 46 | return Err(IoError::new(ErrorKind::NotFound, "File not found")); 47 | } 48 | open(&self.stdin) 49 | } 50 | 51 | pub fn open_stdout(&self) -> IoResult { 52 | if self.stdout.as_os_str().is_empty() { 53 | return Err(IoError::new(ErrorKind::NotFound, "File not found")); 54 | } 55 | open(&self.stdout) 56 | } 57 | 58 | pub fn open_stderr(&self) -> IoResult { 59 | if self.stderr.as_os_str().is_empty() { 60 | return Err(IoError::new(ErrorKind::NotFound, "File not found")); 61 | } 62 | open(&self.stderr) 63 | } 64 | } 65 | 66 | #[cfg(unix)] 67 | #[cfg(test)] 68 | mod tests { 69 | use tempfile::tempdir; 70 | 71 | use super::*; 72 | 73 | #[test] 74 | fn test_determine_rootdir_with_options_file() -> Result<(), Error> { 75 | let namespace = "test_namespace"; 76 | let dir = tempdir()?; 77 | let rootdir = dir.path().join("runwasi"); 78 | let opts = Options { 79 | root: Some(rootdir.clone()), 80 | }; 81 | std::fs::write( 82 | dir.path().join("options.json"), 83 | serde_json::to_string(&opts)?, 84 | )?; 85 | let cfg = InstanceConfig { 86 | bundle: dir.path().to_path_buf(), 87 | namespace: namespace.to_string(), 88 | ..Default::default() 89 | }; 90 | let root = cfg.determine_rootdir("runtime")?; 91 | assert_eq!(root, rootdir.join(namespace)); 92 | Ok(()) 93 | } 94 | 95 | #[test] 96 | fn test_determine_rootdir_without_options_file() -> Result<(), Error> { 97 | let dir = tempdir()?; 98 | let namespace = "test_namespace"; 99 | let cfg = InstanceConfig { 100 | bundle: dir.path().to_path_buf(), 101 | namespace: namespace.to_string(), 102 | ..Default::default() 103 | }; 104 | let root = cfg.determine_rootdir("runtime")?; 105 | assert!(root.is_absolute()); 106 | assert_eq!( 107 | root, 108 | PathBuf::from("/run/containerd/runtime").join(namespace) 109 | ); 110 | Ok(()) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sandbox/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module provides abstractions for managing WebAssembly containers in a sandboxed environment. 2 | //! 3 | //! This module gives you complete control over the container lifecycle and sandboxing, 4 | //! compared to the higher-level container module. It's useful when you need: 5 | //! 6 | //! - Custom sandboxing requirements 7 | //! - Direct control over container lifecycle 8 | //! - Support MacOS or Windows 9 | //! 10 | //! There are also some downsides to using this module: 11 | //! 12 | //! - No precompilation out-of-the-box 13 | //! - Does not support for native Linux containers out-of-the-box 14 | //! - Requires manual handling of cgroup setup 15 | //! 16 | //! ## Key Components 17 | //! 18 | //! - [`Instance`]: Core trait for implementing container lifecycle management 19 | //! - [`cli`]: Command line interface for the containerd shim 20 | //! 21 | //! ## Example Usage 22 | //! 23 | //! ```rust,no_run 24 | //! use containerd_shimkit::sandbox::{Instance, InstanceConfig, Error}; 25 | //! use chrono::{DateTime, Utc}; 26 | //! use std::time::Duration; 27 | //! use anyhow::Result; 28 | //! 29 | //! #[derive(Clone, Default)] 30 | //! struct MyInstance; 31 | //! 32 | //! impl Instance for MyInstance { 33 | //! async fn new(id: String, cfg: &InstanceConfig) -> Result { 34 | //! Ok(MyInstance) 35 | //! } 36 | //! 37 | //! async fn start(&self) -> Result { 38 | //! Ok(1) 39 | //! } 40 | //! 41 | //! async fn kill(&self, signal: u32) -> Result<(), Error> { 42 | //! Ok(()) 43 | //! } 44 | //! 45 | //! async fn delete(&self) -> Result<(), Error> { 46 | //! Ok(()) 47 | //! } 48 | //! 49 | //! async fn wait(&self) -> (u32, DateTime) { 50 | //! (0, Utc::now()) 51 | //! } 52 | //! } 53 | //! ``` 54 | 55 | pub mod cli; 56 | pub mod error; 57 | pub mod instance; 58 | pub mod shim; 59 | pub mod sync; 60 | 61 | pub use error::{Error, Result}; 62 | pub use instance::{Instance, InstanceConfig}; 63 | pub use shim::Config; 64 | pub(crate) use shim::Shim; 65 | 66 | pub(crate) mod instance_utils; 67 | pub(crate) mod oci; 68 | 69 | pub(crate) mod async_utils; 70 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sandbox/oci.rs: -------------------------------------------------------------------------------- 1 | //! Generic helpers for working with OCI specs that can be consumed by any runtime. 2 | 3 | use std::collections::HashMap; 4 | use std::io::{ErrorKind, Write}; 5 | #[cfg(unix)] 6 | use std::os::unix::process::CommandExt; 7 | use std::process; 8 | 9 | use anyhow::Context; 10 | 11 | use super::error::Result; 12 | 13 | fn parse_env(envs: &[String]) -> HashMap { 14 | // make NAME=VALUE to HashMap. 15 | envs.iter() 16 | .filter_map(|e| { 17 | let mut split = e.split('='); 18 | 19 | split.next().map(|key| { 20 | let value = split.collect::>().join("="); 21 | (key.into(), value) 22 | }) 23 | }) 24 | .collect() 25 | } 26 | 27 | pub(crate) fn setup_prestart_hooks(hooks: &Option) -> Result<()> { 28 | if let Some(hooks) = hooks { 29 | let prestart_hooks = hooks.prestart().as_ref().unwrap(); 30 | 31 | for hook in prestart_hooks { 32 | let mut hook_command = process::Command::new(hook.path()); 33 | // Based on OCI spec, the first argument of the args vector is the 34 | // arg0, which can be different from the path. For example, path 35 | // may be "/usr/bin/true" and arg0 is set to "true". However, rust 36 | // command differentiates arg0 from args, where rust command arg 37 | // doesn't include arg0. So we have to make the split arg0 from the 38 | // rest of args. 39 | if let Some((arg0, args)) = hook.args().as_ref().and_then(|a| a.split_first()) { 40 | log::debug!("run_hooks arg0: {:?}, args: {:?}", arg0, args); 41 | 42 | #[cfg(unix)] 43 | { 44 | hook_command.arg0(arg0).args(args); 45 | } 46 | 47 | #[cfg(windows)] 48 | { 49 | if !&hook.path().ends_with(arg0) { 50 | return Err(crate::sandbox::Error::InvalidArgument("Running with arg0 as different name than executable is not supported on Windows due to rust std library process implementation.".to_string())); 51 | } 52 | 53 | hook_command.args(args); 54 | } 55 | } else { 56 | #[cfg(unix)] 57 | hook_command.arg0(hook.path()); 58 | }; 59 | 60 | let envs: HashMap = if let Some(env) = hook.env() { 61 | parse_env(env) 62 | } else { 63 | HashMap::new() 64 | }; 65 | log::debug!("run_hooks envs: {:?}", envs); 66 | 67 | let mut hook_process = hook_command 68 | .env_clear() 69 | .envs(envs) 70 | .stdin(process::Stdio::piped()) 71 | .spawn() 72 | .with_context(|| "Failed to execute hook")?; 73 | 74 | if let Some(stdin) = &mut hook_process.stdin { 75 | // We want to ignore BrokenPipe here. A BrokenPipe indicates 76 | // either the hook is crashed/errored or it ran successfully. 77 | // Either way, this is an indication that the hook command 78 | // finished execution. If the hook command was successful, 79 | // which we will check later in this function, we should not 80 | // fail this step here. We still want to check for all the other 81 | // error, in the case that the hook command is waiting for us to 82 | // write to stdin. 83 | let state = format!("{{ \"pid\": {} }}", std::process::id()); 84 | if let Err(e) = stdin.write_all(state.as_bytes()) { 85 | if e.kind() != ErrorKind::BrokenPipe { 86 | // Not a broken pipe. The hook command may be waiting 87 | // for us. 88 | let _ = hook_process.kill(); 89 | } 90 | } 91 | } 92 | hook_process.wait()?; 93 | } 94 | } 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sandbox/shim/events.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use chrono::{DateTime, TimeZone}; 4 | use containerd_shim::event::Event; 5 | use containerd_shim::publisher::RemotePublisher; 6 | use log::warn; 7 | use protobuf::well_known_types::timestamp::Timestamp; 8 | 9 | pub trait EventSender: Clone + Send + Sync + 'static { 10 | fn send(&self, event: impl Event); 11 | } 12 | 13 | #[derive(Clone)] 14 | pub struct RemoteEventSender { 15 | inner: Arc, 16 | } 17 | 18 | struct Inner { 19 | namespace: String, 20 | publisher: RemotePublisher, 21 | } 22 | 23 | impl RemoteEventSender { 24 | pub fn new(namespace: impl AsRef, publisher: RemotePublisher) -> RemoteEventSender { 25 | let namespace = namespace.as_ref().to_string(); 26 | RemoteEventSender { 27 | inner: Arc::new(Inner { 28 | namespace, 29 | publisher, 30 | }), 31 | } 32 | } 33 | } 34 | 35 | impl EventSender for RemoteEventSender { 36 | fn send(&self, event: impl Event) { 37 | let topic = event.topic(); 38 | let event = Box::new(event); 39 | let publisher = &self.inner.publisher; 40 | if let Err(err) = 41 | publisher.publish(Default::default(), &topic, &self.inner.namespace, event) 42 | { 43 | warn!("failed to publish event, topic: {}: {}", &topic, err) 44 | } 45 | } 46 | } 47 | 48 | pub(super) trait ToTimestamp { 49 | fn to_timestamp(self) -> Timestamp; 50 | } 51 | 52 | impl ToTimestamp for DateTime { 53 | fn to_timestamp(self) -> Timestamp { 54 | Timestamp { 55 | seconds: self.timestamp(), 56 | nanos: self.timestamp_subsec_nanos() as i32, 57 | ..Default::default() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sandbox/shim/instance_data.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use tokio::sync::{OnceCell, RwLock}; 3 | 4 | use crate::sandbox::shim::task_state::TaskState; 5 | use crate::sandbox::{Instance, InstanceConfig, Result}; 6 | 7 | pub(super) struct InstanceData { 8 | pub instance: T, 9 | pub config: InstanceConfig, 10 | pid: OnceCell, 11 | state: RwLock, 12 | } 13 | 14 | impl InstanceData { 15 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Debug"))] 16 | pub async fn new( 17 | id: impl AsRef + std::fmt::Debug, 18 | config: InstanceConfig, 19 | ) -> Result { 20 | let id = id.as_ref().to_string(); 21 | let instance = T::new(id, &config).await?; 22 | Ok(Self { 23 | instance, 24 | config, 25 | pid: OnceCell::default(), 26 | state: RwLock::new(TaskState::Created), 27 | }) 28 | } 29 | 30 | #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), level = "Debug"))] 31 | pub fn pid(&self) -> Option { 32 | self.pid.get().copied() 33 | } 34 | 35 | #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), level = "Debug"))] 36 | pub async fn start(&self) -> Result { 37 | let mut s = self.state.write().await; 38 | s.start()?; 39 | 40 | let res = self.instance.start().await; 41 | 42 | // These state transitions are always `Ok(())` because 43 | // we hold the lock since `s.start()` 44 | let _ = match res { 45 | Ok(pid) => { 46 | let _ = self.pid.set(pid); 47 | s.started() 48 | } 49 | Err(_) => s.stop(), 50 | }; 51 | 52 | res 53 | } 54 | 55 | #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), level = "Debug"))] 56 | pub async fn kill(&self, signal: u32) -> Result<()> { 57 | let mut s = self.state.write().await; 58 | s.kill()?; 59 | 60 | self.instance.kill(signal).await 61 | } 62 | 63 | #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), level = "Debug"))] 64 | pub async fn delete(&self) -> Result<()> { 65 | let mut s = self.state.write().await; 66 | s.delete()?; 67 | 68 | let res = self.instance.delete().await; 69 | 70 | if res.is_err() { 71 | // Always `Ok(())` because we hold the lock since `s.delete()` 72 | let _ = s.stop(); 73 | } 74 | 75 | res 76 | } 77 | 78 | #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), level = "Debug"))] 79 | pub async fn wait(&self) -> (u32, DateTime) { 80 | let res = self.instance.wait().await; 81 | let mut s = self.state.write().await; 82 | *s = TaskState::Exited; 83 | res 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sandbox/shim/mod.rs: -------------------------------------------------------------------------------- 1 | //! The shim exposes the [Config] struct to configure the shim and [OtlpConfig] module to enable tracing if the `opentelemetry` feature is enabled. 2 | 3 | pub use local::Config; 4 | 5 | mod events; 6 | mod instance_data; 7 | mod local; 8 | #[allow(clippy::module_inception)] 9 | mod shim; 10 | mod task_state; 11 | pub(crate) use shim::Shim; 12 | 13 | #[cfg(feature = "opentelemetry")] 14 | mod otel; 15 | #[cfg(feature = "opentelemetry")] 16 | pub(crate) use otel::{Config as OtlpConfig, traces_enabled as otel_traces_enabled}; 17 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sandbox/shim/shim.rs: -------------------------------------------------------------------------------- 1 | use std::env::current_dir; 2 | use std::fmt::Debug; 3 | use std::marker::PhantomData; 4 | 5 | use chrono::Utc; 6 | use containerd_shim::error::Error as ShimError; 7 | use containerd_shim::publisher::RemotePublisher; 8 | use containerd_shim::util::write_address; 9 | use containerd_shim::{self as shim, api}; 10 | use oci_spec::runtime::Spec; 11 | use shim::Flags; 12 | 13 | use crate::sandbox::async_utils::AmbientRuntime as _; 14 | use crate::sandbox::instance::Instance; 15 | use crate::sandbox::shim::events::{RemoteEventSender, ToTimestamp}; 16 | use crate::sandbox::shim::local::Local; 17 | use crate::sandbox::sync::WaitableCell; 18 | 19 | /// Shim implements the [containerd_shim::Shim] trait using `Local` as the task service. 20 | /// 21 | /// It can be used as [`containerd_shim::synchronous::run>()`] to start the shim. 22 | pub struct Shim { 23 | namespace: String, 24 | containerd_address: String, 25 | exit: WaitableCell<()>, 26 | _id: String, 27 | _phantom: PhantomData, 28 | } 29 | 30 | impl Debug for Shim 31 | where 32 | I: Instance + Sync + Send, 33 | { 34 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 35 | write!( 36 | f, 37 | "Shim {{ namespace: {:?}, containerd_address: {:?}, _id: {:?} }}", 38 | self.namespace, self.containerd_address, self._id 39 | ) 40 | } 41 | } 42 | 43 | impl shim::Shim for Shim 44 | where 45 | I: Instance + Sync + Send, 46 | { 47 | type T = Local; 48 | 49 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Info"))] 50 | fn new(_runtime_id: &str, args: &Flags, _config: &mut shim::Config) -> Self { 51 | Shim { 52 | namespace: args.namespace.to_string(), 53 | containerd_address: args.address.clone(), 54 | exit: WaitableCell::new(), 55 | _id: args.id.to_string(), 56 | _phantom: PhantomData, 57 | } 58 | } 59 | 60 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Info"))] 61 | fn start_shim(&mut self, opts: containerd_shim::StartOpts) -> shim::Result { 62 | let dir = current_dir().map_err(|err| ShimError::Other(err.to_string()))?; 63 | let spec = Spec::load(dir.join("config.json")).map_err(|err| { 64 | shim::Error::InvalidArgument(format!("error loading runtime spec: {}", err)) 65 | })?; 66 | 67 | let id = opts.id.clone(); 68 | let grouping = spec 69 | .annotations() 70 | .as_ref() 71 | .and_then(|a| a.get("io.kubernetes.cri.sandbox-id")) 72 | .unwrap_or(&id); 73 | 74 | let (_child, address) = shim::spawn(opts, grouping, vec![])?; 75 | 76 | write_address(&address)?; 77 | 78 | Ok(address) 79 | } 80 | 81 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Info"))] 82 | fn wait(&mut self) { 83 | self.exit.wait().block_on(); 84 | } 85 | 86 | #[cfg_attr( 87 | feature = "tracing", 88 | tracing::instrument(skip(publisher), level = "Info") 89 | )] 90 | fn create_task_service(&self, publisher: RemotePublisher) -> Self::T { 91 | let events = RemoteEventSender::new(&self.namespace, publisher); 92 | let exit = self.exit.clone(); 93 | Local::::new(events, exit, &self.namespace, &self.containerd_address) 94 | } 95 | 96 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Info"))] 97 | fn delete_shim(&mut self) -> shim::Result { 98 | Ok(api::DeleteResponse { 99 | exit_status: 137, 100 | exited_at: Some(Utc::now().to_timestamp()).into(), 101 | ..Default::default() 102 | }) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sandbox/shim/task_state.rs: -------------------------------------------------------------------------------- 1 | use crate::sandbox::Error::FailedPrecondition; 2 | use crate::sandbox::Result; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub(super) enum TaskState { 6 | Created, 7 | Starting, 8 | Started, 9 | Exited, 10 | Deleting, 11 | } 12 | 13 | impl TaskState { 14 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Debug"))] 15 | pub fn start(&mut self) -> Result<()> { 16 | *self = match self { 17 | Self::Created => Ok(Self::Starting), 18 | _ => state_transition_error(*self, Self::Starting), 19 | }?; 20 | Ok(()) 21 | } 22 | 23 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Debug"))] 24 | pub fn kill(&mut self) -> Result<()> { 25 | *self = match self { 26 | Self::Started => Ok(Self::Started), 27 | _ => state_transition_error(*self, "Killing"), 28 | }?; 29 | Ok(()) 30 | } 31 | 32 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Debug"))] 33 | pub fn delete(&mut self) -> Result<()> { 34 | *self = match self { 35 | Self::Created | Self::Exited => Ok(Self::Deleting), 36 | _ => state_transition_error(*self, Self::Deleting), 37 | }?; 38 | Ok(()) 39 | } 40 | 41 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Debug"))] 42 | pub fn started(&mut self) -> Result<()> { 43 | *self = match self { 44 | Self::Starting => Ok(Self::Started), 45 | _ => state_transition_error(*self, Self::Started), 46 | }?; 47 | Ok(()) 48 | } 49 | 50 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Debug"))] 51 | pub fn stop(&mut self) -> Result<()> { 52 | *self = match self { 53 | Self::Started | Self::Starting => Ok(Self::Exited), 54 | // This is for potential failure cases where we want delete to be able to be retried. 55 | Self::Deleting => Ok(Self::Exited), 56 | _ => state_transition_error(*self, Self::Exited), 57 | }?; 58 | Ok(()) 59 | } 60 | } 61 | 62 | fn state_transition_error(from: impl std::fmt::Debug, to: impl std::fmt::Debug) -> Result { 63 | Err(FailedPrecondition(format!( 64 | "invalid state transition: {from:?} => {to:?}" 65 | ))) 66 | } 67 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sys/unix/metrics.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use containerd_shim::cgroup::collect_metrics; 3 | use containerd_shim::util::convert_to_any; 4 | use protobuf::well_known_types::any::Any; 5 | 6 | #[cfg_attr(feature = "tracing", tracing::instrument(level = "Debug"))] 7 | pub fn get_metrics(pid: u32) -> Result { 8 | let metrics = collect_metrics(pid)?; 9 | 10 | let metrics = convert_to_any(Box::new(metrics))?; 11 | Ok(metrics) 12 | } 13 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sys/unix/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::sync::LazyLock; 3 | 4 | pub mod metrics; 5 | pub mod stdio; 6 | 7 | pub static DEFAULT_CONTAINER_ROOT_DIR: LazyLock = 8 | LazyLock::new(|| PathBuf::from("/run/containerd")); 9 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sys/unix/stdio.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, OpenOptions}; 2 | use std::io::Result; 3 | use std::path::Path; 4 | 5 | pub fn open(path: impl AsRef) -> Result { 6 | OpenOptions::new().read(true).write(true).open(path) 7 | } 8 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sys/windows/metrics.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use containerd_shim::util::convert_to_any; 3 | use protobuf::well_known_types::any::Any; 4 | 5 | pub fn get_metrics(_pid: u32) -> Result { 6 | // Create empty message for now 7 | // https://github.com/containerd/rust-extensions/pull/178 8 | let m = protobuf::well_known_types::any::Any::new(); 9 | 10 | let metrics = convert_to_any(Box::new(m))?; 11 | Ok(metrics) 12 | } 13 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sys/windows/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::sync::LazyLock; 3 | 4 | pub mod metrics; 5 | pub mod stdio; 6 | 7 | pub static DEFAULT_CONTAINER_ROOT_DIR: LazyLock = LazyLock::new(|| { 8 | let program_data = std::env::var_os("ProgramData") 9 | .map(PathBuf::from) 10 | .unwrap_or_else(|| PathBuf::from(r"C:\ProgramData")); 11 | program_data.join("containerd").join("state") 12 | }); 13 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/sys/windows/stdio.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, OpenOptions}; 2 | use std::io::Result; 3 | use std::os::windows::fs::OpenOptionsExt as _; 4 | use std::path::Path; 5 | 6 | use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OVERLAPPED; 7 | 8 | pub fn open(path: impl AsRef) -> Result { 9 | // Containerd always passes a named pipe for stdin, stdout, and stderr so we can check if it is a pipe and open with overlapped IO 10 | let mut options = OpenOptions::new(); 11 | options.read(true).write(true); 12 | if path.as_ref().starts_with(r"\\.\pipe\") { 13 | options.custom_flags(FILE_FLAG_OVERLAPPED); 14 | } 15 | options.open(path) 16 | } 17 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/vendor/README.md: -------------------------------------------------------------------------------- 1 | # Vendored Code 2 | 3 | This directory contains vendored code from dependencies that we need to customize or extend. 4 | 5 | ### Usage 6 | 7 | To use the vendored logger instead of the original: 8 | 9 | ```rust 10 | // Instead of 11 | use containerd_shim::logger; 12 | 13 | // Use the vendored version 14 | use crate::vendor::containerd_shim::logger; 15 | ``` 16 | 17 | ### Updating Vendored Code 18 | 19 | When a new version of `containerd-shim` is released, delete the vendored code and use the new version directly. -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/vendor/containerd_shim/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The containerd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! VENDORED CODE FROM containerd-shim v0.8.0 18 | //! 19 | //! This module contains vendored code from the containerd-shim crate. 20 | //! It should be replaced with the upstream version when a new release is available. 21 | //! 22 | //! Source: https://github.com/containerd/rust-extensions/tree/shim-v0.8.0/crates/shim 23 | 24 | pub mod logger; 25 | mod sys; 26 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/vendor/containerd_shim/sys/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The containerd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #[cfg(windows)] 18 | pub(crate) mod windows; 19 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/vendor/containerd_shim/sys/windows/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The containerd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | pub(crate) mod named_pipe_logger; 17 | pub use named_pipe_logger::NamedPipeLogger; 18 | -------------------------------------------------------------------------------- /crates/containerd-shimkit/src/vendor/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The containerd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //! This module contains vendored code from dependencies. 18 | //! 19 | //! The code in this module is vendored from external crates and should be 20 | //! replaced with upstream versions when new releases are available. 21 | 22 | /// Vendored code from the containerd-shim crate. 23 | pub mod containerd_shim; 24 | -------------------------------------------------------------------------------- /crates/oci-tar-builder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oci-tar-builder" 3 | description = "Library that can be used to build OCI tar archives" 4 | version = "0.4.0" 5 | edition.workspace = true 6 | license.workspace = true 7 | readme = "README.md" 8 | homepage.workspace = true 9 | repository.workspace = true 10 | 11 | [dependencies] 12 | tar = { workspace = true } 13 | sha256 = { workspace = true } 14 | log = { workspace = true } 15 | oci-spec = { workspace = true, features = ["runtime"] } 16 | anyhow = { workspace = true } 17 | serde = { workspace = true } 18 | serde_json = { workspace = true } 19 | clap = { version = "4.5.38", features = ["derive"] } 20 | indexmap = "2.9.0" 21 | oci-wasm = { version = "0.2.1", default-features = false, features = ["rustls-tls"] } 22 | tokio = { workspace = true, features = ["rt-multi-thread"] } 23 | 24 | [lib] 25 | path = "src/lib.rs" 26 | 27 | [[bin]] 28 | name = "oci-tar-builder" 29 | path = "src/bin.rs" 30 | -------------------------------------------------------------------------------- /crates/stress-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stress-test" 3 | edition.workspace = true 4 | version.workspace = true 5 | license.workspace = true 6 | readme.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | 10 | [dependencies] 11 | anyhow = { workspace = true } 12 | trapeze = "0.7.6" 13 | prost = "0.13" 14 | prost-types = "0.13" 15 | tokio = { workspace = true, features = ["rt-multi-thread", "macros", "fs", "process", "signal"] } 16 | tokio-async-drop = "0.1.0" 17 | humantime = "2.2.0" 18 | tempfile = { workspace = true } 19 | oci-spec = { workspace = true } 20 | clap = { version = "4", features = ["derive"] } 21 | log = { workspace = true } 22 | env_logger = { workspace = true } 23 | nix = { workspace = true, features = ["process", "signal", "mount"] } 24 | trait-variant = "0.1" 25 | containerd-client = "0.6.0" 26 | tonic = "0.12" 27 | serde_json = { workspace = true } 28 | serde = { workspace = true } 29 | futures = "0.3" 30 | sha256 = { workspace = true } 31 | 32 | 33 | [package.metadata.cargo-machete] 34 | # used by the bindings generated by trapeze 35 | ignored = ["prost", "prost-types"] 36 | -------------------------------------------------------------------------------- /crates/stress-test/README.md: -------------------------------------------------------------------------------- 1 | # Shim stress test 2 | 3 | This crate provides a way to stress test the shim. 4 | 5 | ## Getting started 6 | 7 | ```bash 8 | cargo run -p stress-test -- --help 9 | ``` 10 | 11 | Build some shim 12 | ```bash 13 | make build-wasmtime 14 | ``` 15 | 16 | then stress test it 17 | ```bash 18 | cargo run -p stress-test -- $PWD/target/x86_64-unknown-linux-gnu/debug/containerd-shim-wasmtime-v1 19 | ``` -------------------------------------------------------------------------------- /crates/stress-test/protos/github.com/containerd/containerd/api/runtime/task/v3/shim.proto: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The containerd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | syntax = "proto3"; 18 | 19 | package containerd.task.v3; 20 | 21 | import "google/protobuf/empty.proto"; 22 | import "github.com/containerd/containerd/api/runtime/task/v2/shim.proto"; 23 | 24 | option go_package = "github.com/containerd/containerd/api/runtime/task/v3;task"; 25 | 26 | // Shim service is launched for each container and is responsible for owning the IO 27 | // for the container and its additional processes. The shim is also the parent of 28 | // each container and allows reattaching to the IO and receiving the exit status 29 | // for the container processes. 30 | service Task { 31 | rpc State(containerd.task.v2.StateRequest) returns (containerd.task.v2.StateResponse); 32 | rpc Create(containerd.task.v2.CreateTaskRequest) returns (containerd.task.v2.CreateTaskResponse); 33 | rpc Start(containerd.task.v2.StartRequest) returns (containerd.task.v2.StartResponse); 34 | rpc Delete(containerd.task.v2.DeleteRequest) returns (containerd.task.v2.DeleteResponse); 35 | rpc Pids(containerd.task.v2.PidsRequest) returns (containerd.task.v2.PidsResponse); 36 | rpc Pause(containerd.task.v2.PauseRequest) returns (google.protobuf.Empty); 37 | rpc Resume(containerd.task.v2.ResumeRequest) returns (google.protobuf.Empty); 38 | rpc Checkpoint(containerd.task.v2.CheckpointTaskRequest) returns (google.protobuf.Empty); 39 | rpc Kill(containerd.task.v2.KillRequest) returns (google.protobuf.Empty); 40 | rpc Exec(containerd.task.v2.ExecProcessRequest) returns (google.protobuf.Empty); 41 | rpc ResizePty(containerd.task.v2.ResizePtyRequest) returns (google.protobuf.Empty); 42 | rpc CloseIO(containerd.task.v2.CloseIORequest) returns (google.protobuf.Empty); 43 | rpc Update(containerd.task.v2.UpdateTaskRequest) returns (google.protobuf.Empty); 44 | rpc Wait(containerd.task.v2.WaitRequest) returns (containerd.task.v2.WaitResponse); 45 | rpc Stats(containerd.task.v2.StatsRequest) returns (containerd.task.v2.StatsResponse); 46 | rpc Connect(containerd.task.v2.ConnectRequest) returns (containerd.task.v2.ConnectResponse); 47 | rpc Shutdown(containerd.task.v2.ShutdownRequest) returns (google.protobuf.Empty); 48 | } 49 | -------------------------------------------------------------------------------- /crates/stress-test/protos/github.com/containerd/containerd/api/services/ttrpc/events/v1/events.proto: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The containerd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | syntax = "proto3"; 18 | 19 | package containerd.services.events.ttrpc.v1; 20 | 21 | import "github.com/containerd/containerd/protobuf/plugin/fieldpath.proto"; 22 | import "google/protobuf/any.proto"; 23 | import "google/protobuf/empty.proto"; 24 | import "google/protobuf/timestamp.proto"; 25 | 26 | option go_package = "github.com/containerd/containerd/api/services/ttrpc/events/v1;events"; 27 | 28 | service Events { 29 | // Forward sends an event that has already been packaged into an envelope 30 | // with a timestamp and namespace. 31 | // 32 | // This is useful if earlier timestamping is required or when forwarding on 33 | // behalf of another component, namespace or publisher. 34 | rpc Forward(ForwardRequest) returns (google.protobuf.Empty); 35 | } 36 | 37 | message ForwardRequest { 38 | Envelope envelope = 1; 39 | } 40 | 41 | message Envelope { 42 | option (containerd.plugin.fieldpath) = true; 43 | google.protobuf.Timestamp timestamp = 1; 44 | string namespace = 2; 45 | string topic = 3; 46 | google.protobuf.Any event = 4; 47 | } 48 | -------------------------------------------------------------------------------- /crates/stress-test/protos/github.com/containerd/containerd/api/types/mount.proto: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The containerd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | syntax = "proto3"; 18 | 19 | package containerd.types; 20 | 21 | option go_package = "github.com/containerd/containerd/api/types;types"; 22 | 23 | // Mount describes mounts for a container. 24 | // 25 | // This type is the lingua franca of ContainerD. All services provide mounts 26 | // to be used with the container at creation time. 27 | // 28 | // The Mount type follows the structure of the mount syscall, including a type, 29 | // source, target and options. 30 | message Mount { 31 | // Type defines the nature of the mount. 32 | string type = 1; 33 | 34 | // Source specifies the name of the mount. Depending on mount type, this 35 | // may be a volume name or a host path, or even ignored. 36 | string source = 2; 37 | 38 | // Target path in container 39 | string target = 3; 40 | 41 | // Options specifies zero or more fstab style mount options. 42 | repeated string options = 4; 43 | } 44 | -------------------------------------------------------------------------------- /crates/stress-test/protos/github.com/containerd/containerd/api/types/task/task.proto: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The containerd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | syntax = "proto3"; 18 | 19 | package containerd.v1.types; 20 | 21 | import "google/protobuf/timestamp.proto"; 22 | import "google/protobuf/any.proto"; 23 | 24 | option go_package = "github.com/containerd/containerd/api/types/task"; 25 | 26 | enum Status { 27 | UNKNOWN = 0; 28 | CREATED = 1; 29 | RUNNING = 2; 30 | STOPPED = 3; 31 | PAUSED = 4; 32 | PAUSING = 5; 33 | } 34 | 35 | message Process { 36 | string container_id = 1; 37 | string id = 2; 38 | uint32 pid = 3; 39 | Status status = 4; 40 | string stdin = 5; 41 | string stdout = 6; 42 | string stderr = 7; 43 | bool terminal = 8; 44 | uint32 exit_status = 9; 45 | google.protobuf.Timestamp exited_at = 10; 46 | } 47 | 48 | message ProcessInfo { 49 | // PID is the process ID. 50 | uint32 pid = 1; 51 | // Info contains additional process information. 52 | // 53 | // Info varies by platform. 54 | google.protobuf.Any info = 2; 55 | } 56 | -------------------------------------------------------------------------------- /crates/stress-test/protos/github.com/containerd/containerd/protobuf/plugin/fieldpath.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers for Go with Gadgets 2 | // 3 | // Copyright (c) 2013, The GoGo Authors. All rights reserved. 4 | // http://github.com/gogo/protobuf 5 | // 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions are 8 | // met: 9 | // 10 | // * Redistributions of source code must retain the above copyright 11 | // notice, this list of conditions and the following disclaimer. 12 | // * Redistributions in binary form must reproduce the above 13 | // copyright notice, this list of conditions and the following disclaimer 14 | // in the documentation and/or other materials provided with the 15 | // distribution. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | syntax = "proto2"; 30 | package containerd.plugin; 31 | 32 | import "google/protobuf/descriptor.proto"; 33 | 34 | option go_package = "github.com/containerd/containerd/protobuf/plugin"; 35 | 36 | extend google.protobuf.FileOptions { 37 | optional bool fieldpath_all = 63300; 38 | } 39 | 40 | extend google.protobuf.MessageOptions { 41 | optional bool fieldpath = 64400; 42 | } 43 | -------------------------------------------------------------------------------- /crates/stress-test/src/containerd/containerd.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::Result; 4 | 5 | use super::{Client, Shim}; 6 | 7 | pub struct Containerd { 8 | containerd: Client, 9 | } 10 | 11 | impl Containerd { 12 | pub async fn new(client: Client) -> Result { 13 | Ok(Self { containerd: client }) 14 | } 15 | } 16 | 17 | impl crate::traits::Containerd for Containerd { 18 | type Shim = Shim; 19 | async fn start_shim(&self, shim: impl AsRef + Send) -> Result { 20 | Shim::new(self.containerd.clone(), shim).await 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /crates/stress-test/src/containerd/mod.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | #[allow(clippy::module_inception)] 3 | mod containerd; 4 | mod shim; 5 | mod task; 6 | 7 | pub(crate) use client::Client; 8 | pub use containerd::Containerd; 9 | pub use shim::Shim; 10 | pub use task::Task; 11 | -------------------------------------------------------------------------------- /crates/stress-test/src/containerd/shim.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use anyhow::Result; 4 | use tokio::fs::{remove_file, symlink}; 5 | use tokio_async_drop::tokio_async_drop; 6 | 7 | use super::{Client, Task}; 8 | use crate::containerd; 9 | 10 | pub struct Shim { 11 | link: PathBuf, 12 | runtime: String, 13 | containerd: Client, 14 | } 15 | 16 | impl Shim { 17 | pub(super) async fn new( 18 | containerd: containerd::Client, 19 | binary: impl AsRef, 20 | ) -> Result { 21 | let pid = std::process::id(); 22 | let runtime = format!("io.containerd.runwasi{pid}.v1"); 23 | let link = format!("/usr/local/bin/containerd-shim-runwasi{pid}-v1"); 24 | let link = PathBuf::from(link); 25 | symlink(binary.as_ref().canonicalize()?, &link).await?; 26 | 27 | Ok(Self { 28 | containerd, 29 | link, 30 | runtime, 31 | }) 32 | } 33 | } 34 | 35 | impl Drop for Shim { 36 | fn drop(&mut self) { 37 | tokio_async_drop!({ 38 | let _ = remove_file(&self.link).await; 39 | }) 40 | } 41 | } 42 | 43 | impl crate::traits::Shim for Shim { 44 | type Task = Task; 45 | 46 | async fn task>( 47 | &self, 48 | image: impl Into, 49 | args: impl IntoIterator, 50 | ) -> Result { 51 | Task::new(self.containerd.clone(), &self.runtime, image, args).await 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/stress-test/src/mocks/containerd.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::Result; 4 | use tempfile::{TempDir, tempdir}; 5 | use trapeze::{Server, ServerHandle, service}; 6 | 7 | use super::Shim; 8 | use crate::containerd; 9 | use crate::protos::containerd::services::events::ttrpc::v1::{Events, ForwardRequest}; 10 | 11 | struct EventsService; 12 | 13 | impl Events for EventsService { 14 | async fn forward(&self, forward_request: ForwardRequest) -> trapeze::Result<()> { 15 | log::info!("forward_request: {forward_request:?}"); 16 | Ok(()) 17 | } 18 | } 19 | 20 | pub struct Containerd { 21 | dir: TempDir, 22 | _server: ServerHandle, 23 | verbose: bool, 24 | containerd: containerd::Client, 25 | } 26 | 27 | impl Containerd { 28 | pub async fn new(client: containerd::Client, verbose: bool) -> Result { 29 | let dir = tempdir()?; 30 | let socket = dir.path().join("containerd.sock.ttrpc"); 31 | 32 | let _server = Server::new() 33 | .register(service!(EventsService: Events)) 34 | .bind(format!("unix://{}", socket.display())) 35 | .await?; 36 | 37 | Ok(Self { 38 | dir, 39 | _server, 40 | verbose, 41 | containerd: client, 42 | }) 43 | } 44 | } 45 | 46 | impl crate::traits::Containerd for Containerd { 47 | type Shim = Shim; 48 | async fn start_shim(&self, shim: impl AsRef + Send) -> Result { 49 | Shim::new(self.containerd.clone(), &self.dir, self.verbose, shim).await 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/stress-test/src/mocks/mod.rs: -------------------------------------------------------------------------------- 1 | mod containerd; 2 | mod shim; 3 | mod task; 4 | mod task_client; 5 | 6 | pub use containerd::Containerd; 7 | pub use shim::Shim; 8 | pub use task::Task; 9 | -------------------------------------------------------------------------------- /crates/stress-test/src/mocks/task_client.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, bail}; 2 | use trapeze::{Client, Code}; 3 | 4 | use crate::protos::containerd::task::v2::*; 5 | use crate::protos::containerd::task::v3::Task as TaskV3; 6 | 7 | #[derive(Clone, Copy)] 8 | enum Version { 9 | V2, 10 | V3, 11 | } 12 | 13 | #[derive(Clone)] 14 | pub struct TaskClient { 15 | client: Client, 16 | version: Version, 17 | } 18 | 19 | macro_rules! multiplex { 20 | ($obj:ident.$method:ident ( $req:ident ) $($rest:tt)*) => {{ 21 | match $obj.version { 22 | Version::V2 => { 23 | trapeze::as_client!(&$obj.client: Task) 24 | .$method($req) 25 | .await 26 | } 27 | Version::V3 => { 28 | trapeze::as_client!(&$obj.client: TaskV3) 29 | .$method($req) 30 | .await 31 | } 32 | } 33 | }}; 34 | } 35 | 36 | impl TaskClient { 37 | pub async fn connect(address: impl AsRef) -> Result { 38 | let client = Client::connect(address).await?; 39 | 40 | let version = 'v: { 41 | let task = trapeze::as_client!(&client: Task); 42 | let Err(status) = task.delete(DeleteRequest::default()).await else { 43 | bail!("unexpected shim response") 44 | }; 45 | if status.code() != Code::Unimplemented { 46 | break 'v Version::V2; 47 | } 48 | let task = trapeze::as_client!(&client: TaskV3); 49 | let Err(status) = task.delete(DeleteRequest::default()).await else { 50 | bail!("unexpected shim response") 51 | }; 52 | if status.code() != Code::Unimplemented { 53 | break 'v Version::V3; 54 | } 55 | bail!("unknown task service version") 56 | }; 57 | 58 | Ok(Self { version, client }) 59 | } 60 | 61 | pub async fn shutdown(&self, req: ShutdownRequest) -> trapeze::Result<()> { 62 | multiplex!(self.shutdown(req)) 63 | } 64 | 65 | pub async fn create(&self, req: CreateTaskRequest) -> trapeze::Result { 66 | multiplex!(self.create(req)) 67 | } 68 | 69 | pub async fn start(&self, req: StartRequest) -> trapeze::Result { 70 | multiplex!(self.start(req)) 71 | } 72 | 73 | pub async fn wait(&self, req: WaitRequest) -> trapeze::Result { 74 | multiplex!(self.wait(req)) 75 | } 76 | 77 | pub async fn delete(&self, req: DeleteRequest) -> trapeze::Result { 78 | multiplex!(self.delete(req)) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /crates/stress-test/src/protos.rs: -------------------------------------------------------------------------------- 1 | trapeze::include_protos!( 2 | [ 3 | "protos/github.com/containerd/containerd/api/runtime/task/v2/shim.proto", 4 | "protos/github.com/containerd/containerd/api/runtime/task/v3/shim.proto", 5 | "protos/github.com/containerd/containerd/api/services/ttrpc/events/v1/events.proto", 6 | ], 7 | ["protos"] 8 | ); 9 | -------------------------------------------------------------------------------- /crates/stress-test/src/traits.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::Result; 4 | 5 | #[trait_variant::make(Send)] 6 | pub trait Containerd { 7 | type Shim: Shim; 8 | async fn start_shim(&self, shim: impl AsRef + Send) -> Result; 9 | } 10 | 11 | #[trait_variant::make(Send)] 12 | pub trait Shim { 13 | type Task: Task; 14 | async fn task>( 15 | &self, 16 | image: impl Into + Send, 17 | args: impl IntoIterator + Send, 18 | ) -> Result; 19 | } 20 | 21 | #[trait_variant::make(Send)] 22 | pub trait Task { 23 | async fn create(&self) -> Result<()>; 24 | async fn start(&self) -> Result<()>; 25 | async fn wait(&self) -> Result<()>; 26 | async fn delete(&self) -> Result<()>; 27 | } 28 | -------------------------------------------------------------------------------- /crates/stress-test/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::future::{Future, pending}; 2 | use std::sync::atomic::{AtomicUsize, Ordering}; 3 | use std::time::Duration; 4 | 5 | use anyhow::Result; 6 | use nix::sys::signal::Signal::SIGKILL; 7 | use nix::sys::signal::kill; 8 | use nix::sys::wait::{WaitPidFlag, waitpid}; 9 | use nix::unistd::Pid; 10 | use tokio::sync::Mutex; 11 | use tokio::time::sleep; 12 | 13 | static COUNTER: AtomicUsize = AtomicUsize::new(0); 14 | 15 | pub fn make_task_id() -> String { 16 | let pid = std::process::id(); 17 | let n = COUNTER.fetch_add(1, Ordering::SeqCst); 18 | format!("shim-stress-test-{pid}-task-{n}") 19 | } 20 | 21 | pub async fn reap_children() -> Result<()> { 22 | let pid = std::process::id(); 23 | loop { 24 | let list: Vec = tokio::fs::read_to_string(format!("/proc/{pid}/task/{pid}/children")) 25 | .await? 26 | .split_whitespace() 27 | .filter_map(|x| x.parse().ok()) 28 | .collect(); 29 | 30 | if list.is_empty() { 31 | return Ok(()); 32 | } 33 | 34 | for pid in list { 35 | let pid = Pid::from_raw(pid as _); 36 | let _ = kill(pid, SIGKILL); 37 | let _ = waitpid(pid, Some(WaitPidFlag::WNOHANG)); 38 | } 39 | } 40 | } 41 | 42 | pub async fn watchdog(timeout: Duration) { 43 | if timeout.is_zero() { 44 | pending().await 45 | } else { 46 | sleep(timeout).await 47 | } 48 | } 49 | 50 | pub struct RunOnce(Mutex); 51 | 52 | impl RunOnce { 53 | pub fn new() -> Self { 54 | Self(Mutex::new(false)) 55 | } 56 | 57 | pub async fn try_run(&self, fut: impl Future>) -> Result<()> { 58 | let mut done = self.0.lock().await; 59 | if !*done { 60 | fut.await?; 61 | *done = true; 62 | } 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/wasi-demo-app/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = ["wasm32-wasip1"] -------------------------------------------------------------------------------- /crates/wasi-demo-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasi-demo-app" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [build-dependencies] 7 | tar = { workspace = true, optional = true } 8 | sha256 = { workspace = true, optional = true } 9 | log = { workspace = true, optional = true } 10 | env_logger = { workspace = true, optional = true } 11 | oci-spec = { workspace = true, optional=true } 12 | oci-tar-builder = { optional = true, path = "../oci-tar-builder" } 13 | anyhow = { workspace = true, optional = true } 14 | 15 | [features] 16 | default = [] 17 | oci-v1-tar = ["default", "tar", "sha256", "log", "env_logger", "oci-spec", "oci-tar-builder", "anyhow"] 18 | -------------------------------------------------------------------------------- /crates/wasi-demo-app/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/crates/wasi-demo-app/README.md -------------------------------------------------------------------------------- /crates/wasi-demo-app/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "oci-v1-tar")] 2 | use { 3 | anyhow::Context, 4 | oci_spec::image::{self as spec, Arch}, 5 | oci_tar_builder::Builder, 6 | sha256::try_digest, 7 | std::env, 8 | std::fs::File, 9 | std::path::PathBuf, 10 | }; 11 | 12 | #[cfg(not(feature = "oci-v1-tar"))] 13 | fn main() {} 14 | 15 | #[cfg(feature = "oci-v1-tar")] 16 | fn main() { 17 | env_logger::init(); 18 | 19 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 20 | let p = out_dir.join("img.tar"); 21 | let bin_output_dir = out_dir 22 | .parent() 23 | .unwrap() 24 | .parent() 25 | .unwrap() 26 | .parent() 27 | .unwrap(); 28 | 29 | let app_path = bin_output_dir.join("wasi-demo-app.wasm"); 30 | let layer_path = out_dir.join("layer.tar"); 31 | tar::Builder::new(File::create(&layer_path).unwrap()) 32 | .append_path_with_name(&app_path, "wasi-demo-app.wasm") 33 | .unwrap(); 34 | 35 | let mut builder = Builder::default(); 36 | 37 | builder.add_layer(&layer_path); 38 | 39 | let config = spec::ConfigBuilder::default() 40 | .entrypoint(vec!["/wasi-demo-app.wasm".to_owned()]) 41 | .build() 42 | .unwrap(); 43 | 44 | let layer_digest = try_digest(layer_path.as_path()).unwrap(); 45 | let img = spec::ImageConfigurationBuilder::default() 46 | .config(config) 47 | .os("wasip1") 48 | .architecture(Arch::Wasm) 49 | .rootfs( 50 | spec::RootFsBuilder::default() 51 | .diff_ids(vec!["sha256:".to_owned() + &layer_digest]) 52 | .build() 53 | .unwrap(), 54 | ) 55 | .build() 56 | .context("failed to build image configuration") 57 | .unwrap(); 58 | 59 | builder.add_config( 60 | img, 61 | "ghcr.io/containerd/runwasi/wasi-demo-app:latest".to_string(), 62 | spec::MediaType::ImageConfig, 63 | ); 64 | 65 | let f = File::create(&p).unwrap(); 66 | builder.build(f).unwrap(); 67 | std::fs::rename(&p, bin_output_dir.join("img.tar")).unwrap(); 68 | } 69 | -------------------------------------------------------------------------------- /crates/wasi-demo-app/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::prelude::*; 3 | use std::thread::sleep; 4 | use std::time::Duration; 5 | use std::{env, process}; 6 | 7 | fn main() { 8 | let args: Vec<_> = env::args().collect(); 9 | let mut cmd = "daemon"; 10 | if args.len() >= 2 { 11 | cmd = &args[1]; 12 | } 13 | 14 | match cmd { 15 | "echo" => println!("{}", &args[2..].join(" ")), 16 | "sleep" => sleep(Duration::from_secs_f64(args[2].parse::().unwrap())), 17 | "exit" => process::exit(args[2].parse::().unwrap()), 18 | "write" => { 19 | let mut file = File::create(&args[2]).unwrap(); 20 | file.write_all(args[3..].join(" ").as_bytes()).unwrap(); 21 | } 22 | "daemon" => loop { 23 | println!( 24 | "This is a song that never ends.\nYes, it goes on and on my friends.\nSome people \ 25 | started singing it not knowing what it was,\nSo they'll continue singing it \ 26 | forever just because...\n" 27 | ); 28 | sleep(Duration::from_secs(1)); 29 | }, 30 | _ => { 31 | eprintln!("unknown command: {0}", args[1]); 32 | process::exit(1); 33 | } 34 | } 35 | 36 | eprintln!("exiting"); 37 | } 38 | -------------------------------------------------------------------------------- /cross/Dockerfile.gnu: -------------------------------------------------------------------------------- 1 | ARG CROSS_BASE_IMAGE 2 | ARG CROSS_DEB_ARCH 3 | FROM $CROSS_BASE_IMAGE 4 | 5 | ARG CROSS_DEB_ARCH 6 | RUN dpkg --add-architecture ${CROSS_DEB_ARCH} && \ 7 | apt-get -y update && \ 8 | apt-get install -y pkg-config protobuf-compiler libseccomp-dev:${CROSS_DEB_ARCH} libzstd-dev:${CROSS_DEB_ARCH} libssl-dev 9 | -------------------------------------------------------------------------------- /cross/Dockerfile.musl: -------------------------------------------------------------------------------- 1 | ARG CROSS_BASE_IMAGE 2 | FROM $CROSS_BASE_IMAGE 3 | 4 | COPY --from=jorgeprendes420/apk-anywhere / / 5 | ENV MARCH=${CROSS_CMAKE_SYSTEM_PROCESSOR} 6 | RUN apk-init ${MARCH} ${CROSS_SYSROOT} 7 | 8 | # configure libsecccomp-rs to use static linking 9 | RUN apk-${MARCH} add libseccomp-static libseccomp-dev openssl-dev 10 | ENV LIBSECCOMP_LINK_TYPE="static" 11 | ENV LIBSECCOMP_LIB_PATH="${CROSS_SYSROOT}/lib" 12 | 13 | # configure wasmedge to link stdc++ statically 14 | RUN apk-${MARCH} add g++ 15 | ENV WASMEDGE_DEP_STDCXX_LINK_TYPE="static" 16 | ENV WASMEDGE_DEP_STDCXX_LIB_PATH="${CROSS_SYSROOT}/lib" 17 | 18 | # install zstd-static 19 | RUN apk-${MARCH} add --upgrade zstd-static 20 | 21 | # wasmedge (through llvm) needs some symbols defined in libgcc 22 | RUN mkdir /.cargo && cat <<'EOF' > /.cargo/config.toml 23 | [target.'cfg(target_env = "musl")'] 24 | rustflags = ["-Clink-arg=-lgcc"] 25 | EOF 26 | 27 | RUN apt-get -y update && \ 28 | apt-get install -y pkg-config protobuf-compiler 29 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Runwasi Authors"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Runwasi Developer Documentation" 7 | description = "Documentation for runwasi" 8 | 9 | [preprocessor] 10 | 11 | [preprocessor.mermaid] 12 | command = "mdbook-mermaid" 13 | 14 | [output] 15 | 16 | [output.html] 17 | additional-js = ["mermaid.min.js", "mermaid-init.js"] 18 | git-repository-url = "https://github.com/containerd/runwasi" 19 | edit-url-template = "https://github.com/containerd/runwasi/edit/main/docs/{path}" 20 | default-theme = "light" 21 | preferred-dark-theme = "navy" 22 | copy-fonts = true 23 | no-section-label = false 24 | curly-quotes = true 25 | 26 | [output.html.fold] 27 | enable = true 28 | level = 1 29 | 30 | [output.html.search] 31 | enable = true 32 | limit-results = 30 33 | teaser-word-count = 30 34 | use-boolean-and = true 35 | boost-title = 2 36 | boost-hierarchy = 1 37 | boost-paragraph = 1 38 | expand = true 39 | heading-split-level = 3 40 | -------------------------------------------------------------------------------- /docs/mermaid-init.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const darkThemes = ['ayu', 'navy', 'coal']; 3 | const lightThemes = ['light', 'rust']; 4 | 5 | const classList = document.getElementsByTagName('html')[0].classList; 6 | 7 | let lastThemeWasLight = true; 8 | for (const cssClass of classList) { 9 | if (darkThemes.includes(cssClass)) { 10 | lastThemeWasLight = false; 11 | break; 12 | } 13 | } 14 | 15 | const theme = lastThemeWasLight ? 'default' : 'dark'; 16 | mermaid.initialize({ startOnLoad: true, theme }); 17 | 18 | // Simplest way to make mermaid re-render the diagrams in the new theme is via refreshing the page 19 | 20 | for (const darkTheme of darkThemes) { 21 | document.getElementById(darkTheme).addEventListener('click', () => { 22 | if (lastThemeWasLight) { 23 | window.location.reload(); 24 | } 25 | }); 26 | } 27 | 28 | for (const lightTheme of lightThemes) { 29 | document.getElementById(lightTheme).addEventListener('click', () => { 30 | if (!lastThemeWasLight) { 31 | window.location.reload(); 32 | } 33 | }); 34 | } 35 | })(); 36 | -------------------------------------------------------------------------------- /docs/src/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ../../CONTRIBUTING.md -------------------------------------------------------------------------------- /docs/src/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /docs/src/RELEASE.md: -------------------------------------------------------------------------------- 1 | ../../RELEASE.md -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](./README.md) 4 | 5 | # Getting Started 6 | - [Installation](./getting-started/installation.md) 7 | - [Demos](./getting-started/demos.md) 8 | - [Quickstart with Kubernetes](./getting-started/quickstart.md) 9 | - [Windows Setup](./windows-getting-started.md) 10 | 11 | # Developer Guide 12 | - [Architecture Overview](./developer/architecture.md) 13 | - [OCI Integration](./oci-decision-flow.md) 14 | - [Contributing](./CONTRIBUTING.md) 15 | - [Documentation Guidelines](./developer/docs.md) 16 | - [Release Process](./RELEASE.md) 17 | - [Project Roadmap](./developer/roadmap.md) 18 | 19 | # Operational 20 | - [Benchmarks](./benchmarks.md) 21 | - [OpenTelemetry Integration](./opentelemetry.md) 22 | - [Troubleshooting](./resources/troubleshooting.md) 23 | 24 | # Community 25 | - [FAQ](./resources/faq.md) 26 | - [Community](./resources/community.md) -------------------------------------------------------------------------------- /docs/src/art/logo/runwasi_icon1.svg: -------------------------------------------------------------------------------- 1 | ../../../../art/logo/runwasi_icon1.svg -------------------------------------------------------------------------------- /docs/src/art/logo/runwasi_icon3.svg: -------------------------------------------------------------------------------- 1 | ../../../../art/logo/runwasi_icon3.svg -------------------------------------------------------------------------------- /docs/src/assets/benchmark-website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/docs/src/assets/benchmark-website.png -------------------------------------------------------------------------------- /docs/src/assets/runwasi-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/docs/src/assets/runwasi-architecture.png -------------------------------------------------------------------------------- /docs/src/assets/wasmtime-shim-jeager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/docs/src/assets/wasmtime-shim-jeager.png -------------------------------------------------------------------------------- /docs/src/assets/wasmtime-shim-tracing-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containerd/runwasi/4dd6f7d2450c1babeecf2963994a75224dcca97a/docs/src/assets/wasmtime-shim-tracing-main.png -------------------------------------------------------------------------------- /docs/src/developer/docs.md: -------------------------------------------------------------------------------- 1 | # Documentation website 2 | 3 | This project uses the [mdBook](https://rust-lang.github.io/mdBook/) tool to generate a documentation website from 4 | markdown files. The website is hosted on GitHub Pages and is available at the 5 | following URL: [https://runwasi.dev/](https://runwasi.dev/). 6 | 7 | ## Building the documentation 8 | 9 | To build the documentation, you need to have the `mdbook` tool installed. You 10 | can install it using the following command: 11 | 12 | ```bash 13 | cargo install mdbook 14 | ``` 15 | 16 | Once you have `mdbook` installed, you can build the documentation by running the 17 | following command: 18 | 19 | ```bash 20 | mdbook build 21 | ``` 22 | 23 | This will generate the documentation in the `book` directory. You can verify 24 | locally by running: 25 | 26 | ```bash 27 | mdbook serve 28 | ``` 29 | which will start a local web server at `http://localhost:3000` where you can 30 | view the documentation. 31 | 32 | ## Contributing 33 | 34 | If you would like to contribute to the documentation, you can do so by editing 35 | the markdown files in the `src` directory. Once you have made your changes, you 36 | can build the documentation as described above and verify that your changes are 37 | correct. 38 | 39 | If you are happy with your changes, you can submit a pull request to the `main` 40 | branch of the repository. Once your pull request is merged, the changes will be 41 | automatically published to the documentation website. 42 | 43 | ## Deploying the documentation 44 | 45 | The documentation is automatically deployed to GitHub Pages when changes are 46 | merged to the `main` branch. 47 | To deploy the documentation, the following github actions are used: 48 | 49 | - [actions-mdbook](https://github.com/peaceiris/actions-mdbook) for building the 50 | documentation. 51 | - [actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) for 52 | deploying the documentation to GitHub Pages. 53 | -------------------------------------------------------------------------------- /docs/src/developer/roadmap.md: -------------------------------------------------------------------------------- 1 | # Project Roadmap 2 | 3 | This document outlines the future development plans for Runwasi. It serves as a guide for contributors and users to understand where the project is headed. 4 | 5 | ## Current Status and 1.0 Release Plan 6 | 7 | Runwasi is currently in active development and targeting a stable 1.0 release in the upcoming months. The project tracks development priorities and progress on the [Runwasi Project Board](https://github.com/orgs/containerd/projects/8/views/1). 8 | 9 | The 1.0 release will mark an important milestone for Runwasi, providing a stable API and features that are ready for production use. 10 | 11 | ## Contributing to the Roadmap 12 | 13 | We welcome contributions to help us achieve these goals! If you're interested in contributing: 14 | 15 | 1. Check the [Project Board](https://github.com/orgs/containerd/projects/8/views/1) for current priorities 16 | 2. Join discussions on [GitHub Issues](https://github.com/containerd/runwasi/issues) 17 | 3. Participate in [community meetings and discussions](../resources/community.md) 18 | 4. Review the [Contributing Guide](../CONTRIBUTING.md) for detailed information on how to get involved 19 | -------------------------------------------------------------------------------- /docs/src/getting-started/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | This guide will help you install and set up Runwasi on your system. 4 | 5 | ## Prerequisites 6 | 7 | Before installing Runwasi, ensure you have the following prerequisites installed: 8 | 9 | - [Rust](https://www.rust-lang.org/tools/install) (stable) 10 | - [containerd](https://github.com/containerd/containerd/blob/main/docs/getting-started.md) 11 | 12 | Additionally, check the [contributing guide](../CONTRIBUTING.md#setting-up-your-local-environment) for detailed instructions on setting up your environment with all required dependencies. 13 | 14 | ## Installation Methods 15 | 16 | ### Option 1: Installing Prebuilt Binaries 17 | 18 | The easiest way to get started is to download prebuilt binaries from the [GitHub releases page](https://github.com/containerd/runwasi/releases). 19 | 20 | 1. Navigate to the [releases page](https://github.com/containerd/runwasi/releases) 21 | 2. Download the appropriate shim for your preferred WebAssembly runtime: 22 | - `containerd-shim-wasmtime-v1` - for Wasmtime runtime 23 | - `containerd-shim-wasmedge-v1` - for WasmEdge runtime 24 | - `containerd-shim-wasmer-v1` - for Wasmer runtime 25 | - `containerd-shim-wamr-v1` - for WebAssembly Micro Runtime (WAMR) 26 | 27 | 3. Make the binary executable and move it to your PATH: 28 | 29 | ```bash 30 | chmod +x containerd-shim-wasmtime-v1 31 | sudo install containerd-shim-wasmtime-v1 /usr/local/bin/ 32 | ``` 33 | 34 | 4. Verify the binary signature (recommended): 35 | 36 | ```bash 37 | # Verify using cosign 38 | cosign verify-blob \ 39 | --signature containerd-shim-wasmtime-v1.sig \ 40 | --certificate containerd-shim-wasmtime-v1.pem \ 41 | --certificate-identity https://github.com/containerd/runwasi/.github/workflows/action-build.yml@refs/heads/main \ 42 | --certificate-oidc-issuer https://token.actions.githubusercontent.com \ 43 | containerd-shim-wasmtime-v1 44 | ``` 45 | 46 | ### Option 2: Building from Source 47 | 48 | To build and install Runwasi from source: 49 | 50 | 1. Clone the repository: 51 | 52 | ```bash 53 | git clone https://github.com/containerd/runwasi.git 54 | cd runwasi 55 | ``` 56 | 57 | 2. Build the shim for your preferred runtime: 58 | 59 | ```bash 60 | make build 61 | ``` 62 | 63 | > Note: `make build` will only build shims for all runtimes. You can specify which runtime to build with `make build-wasmtime`, `make build-wasmer`, `make build-wasmedge`, `make build-wamr` etc. 64 | 65 | 3. Install the binary: 66 | 67 | ```bash 68 | sudo make install 69 | ``` 70 | 71 | The `make install` command copies the binary to $PATH 72 | 73 | ## Testing Your Installation 74 | 75 | After installation, you can test your setup by pulling and running a test image: 76 | 77 | 1. Pull the test image: 78 | 79 | ```bash 80 | sudo ctr pull ghcr.io/containerd/runwasi/wasi-demo-app:latest 81 | ``` 82 | 83 | 2. Run a test container: 84 | 85 | ```bash 86 | sudo ctr run --rm --runtime=io.containerd.wasmtime.v1 ghcr.io/containerd/runwasi/wasi-demo-app:latest testwasm 87 | ``` 88 | 89 | You should see output from the demo application. 90 | 91 | ## Next Steps 92 | 93 | Now that you have runwasi shims installed, you can proceed to the [Demos](./demos.md) to learn how to run WebAssembly workloads with Runwasi. 94 | -------------------------------------------------------------------------------- /docs/src/oci-decision-flow.md: -------------------------------------------------------------------------------- 1 | # OCI pre-compilation 2 | 3 | The OCI images layers are loaded from containerd. If the runtime supports pre-compilation the images will be precompiled and cached using the containerd content store. 4 | 5 | ```mermaid 6 | graph TD 7 | start[Task new] 8 | imgconfig[Load image config from containerd] 9 | iswasm{Arch==wasm?} 10 | alreadycompiled{Does image label for shim runtime version exist? runwasi.io/precompiled/runtime/version} 11 | startcontainer[Create Container] 12 | precompiledenabled{Is precompiling enabled in shim?} 13 | precompiledenabled2{Is precompiling enabled in shim?} 14 | fetchcache[Fetch cached precompiled layer from containerd content store] 15 | precompile[Precompile using wasm runtime] 16 | loadoci[Load OCI layers from containerd] 17 | storecache[Store precompiled layer in containerd content store] 18 | 19 | start --> imgconfig --> iswasm 20 | iswasm -- yes --> precompiledenabled 21 | iswasm -- no. wasm will be loaded from file inside image --> startcontainer 22 | 23 | precompiledenabled -- yes --> alreadycompiled 24 | precompiledenabled -- no --> loadoci --> precompiledenabled2 25 | 26 | alreadycompiled -- yes --> fetchcache --> startcontainer 27 | alreadycompiled -- no --> loadoci 28 | 29 | precompiledenabled2 -- yes --> precompile --> storecache --> startcontainer 30 | precompiledenabled2 -- no --> startcontainer 31 | ``` 32 | 33 | Once a wasm module or component is pre-compiled it will remain in the containerd content store until the original image is removed from containerd. There is a small disk overhead associated with this but it reduces the complexity of managing stored versions during upgrades. 34 | 35 | To view the images in containerd that have associated pre-compilations: 36 | 37 | ```bash 38 | sudo ctr i ls | grep "runwasi.io" 39 | ghcr.io/containerd/runwasi/wasi-demo-oci:latest application/vnd.oci.image.manifest.v1+json 40 | sha256:60fccd77070dfeb682a1ebc742e9d677fc452b30a6b99188b081c968992394ce 2.4 MiB wasi/wasm 41 | runwasi.io/precompiled/wasmtime/0.3.1=sha256:b36753ab5a46f26f6bedb81b8a7b489cede8fc7386f1398706782e225fd0a98e 42 | 43 | # query for the sha in the label 44 | sudo ctr content ls | grep "b36753ab5a46f26f6bedb81b8a7b489cede8fc7386f139870" 45 | sha256:60fccd77070dfeb682a1ebc742e9d677fc452b30a6b99188b081c968992394ce 561B 2 months containerd.io/gc.ref.content.0=sha256:a3c18cd551d54d3cfbf67acc9e8f7ef5761e76827fe7c1ae163fca0193be88b3,containerd.io/gc.ref.content.config=sha256:85b7f2b562fe8665ec9d9e6d47ab0b24e2315627f5f558d298475c4038d71e8b,containerd.io/gc.ref.content.precompile=sha256:b36753ab5a46f26f6bedb81b8a7b489cede8fc7386f1398706782e225fd0a98e 46 | sha256:b36753ab5a46f26f6bedb81b8a7b489cede8fc7386f1398706782e225fd0a98e 626.4kB 3 days runwasi.io/precompiled=sha256:60fccd77070dfeb682a1ebc742e9d677fc452b30a6b99188b081c968992394ce 47 | ``` -------------------------------------------------------------------------------- /docs/src/opentelemetry.md: -------------------------------------------------------------------------------- 1 | # OpenTelemetry 2 | 3 | [OpenTelemetry](https://opentelemetry.io/) is a set of libraries, agents, and instrumentation to provide observability (metrics, logs and traces) in applications. 4 | 5 | `containerd-shim-wasm` crate has a set of APIs to enable OpenTelemetry tracing in the shim. This document is a guide on how to use OpenTelemetry tracing in your shim. 6 | 7 | ## Usage 8 | 9 | > Wasmtime shim v0.5.0 has OpenTelemetry tracing enabled by default 10 | 11 | To use OpenTelemetry tracing in your shim, you need to use the `opentelemetry` feature in the `containerd-shim-wasm` crate. 12 | ```toml 13 | containerd-shim-wasm = { workspace = true, features = ["opentelemetry"] } 14 | ``` 15 | 16 | Then, you may use the `containerd_shim_wasm::sandbox::cli::shim_main` function to run the shim with OpenTelemetry tracing. 17 | 18 | ```rust 19 | fn main() { 20 | shim_main::(version!(), revision!(), None); 21 | } 22 | ``` 23 | 24 | You may also use the `containerd_shim_wasm::sandbox::shim::OtlpConfig` struct to configure the OpenTelemetry tracing manually. 25 | 26 | ### Running containerd with OpenTelemetry 27 | 28 | You can configure / run containerd with OpenTelemetry tracing. Please refer to the [containerd documentation](https://github.com/containerd/containerd/blob/v2.0.0/docs/tracing.md#sending-traces-from-containerd-daemon) for more information. 29 | 30 | ```sh 31 | OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 32 | 33 | # by default, Containerd uses the `http/protobuf` protocol 34 | ``` 35 | 36 | `Runwasi` will automatically pick up the environment variables and start exporting traces to the specified endpoint. 37 | 38 | ### Jeager Exporter 39 | 40 | You may use Jeager exporter to see the traces in the Jeager UI. 41 | 42 | ```sh 43 | docker run -d -p16686:16686 -p4317:4317 -p4318:4318 -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:latest 44 | ``` 45 | 46 | You can access the Jeager UI at `http://localhost:16686`. 47 | 48 | ### Demo 49 | 50 | Assuming you installed the `containerd-shim-wasmtime-v1` shim binary and the demo wasm image following README.md instructions, 51 | you can run the wasmtime shim with OpenTelemetry tracing by running the following command 52 | 53 | ```sh 54 | sudo ctr run --net-host --rm --runtime=io.containerd.wasmtime.v1 ghcr.io/containerd/runwasi/wasi-demo-app:latest testwasm /wasi-demo-app.wasm sleep 3 55 | ``` 56 | 57 | ![A screenshot of the jeager UI for the wasmtime shim](assets/wasmtime-shim-jeager.png) 58 | 59 | ![A screenshot of the jeager UI for traces of the main function call of the wasmtime shim](assets/wasmtime-shim-tracing-main.png) 60 | 61 | ## Environment Variables 62 | 63 | `Runwasi` uses the standard [OTLP environment variables](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/) to configure the OTLP exporter endpoint. The following environment variables are supported: 64 | 65 | - `OTEL_EXPORTER_OTLP_ENDPOINT` - A base endpoint to send trace data to. 66 | - `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` - The endpoint to send trace data to. Overrides `OTEL_EXPORTER_OTLP_ENDPOINT`. 67 | - `OTEL_EXPORTER_OTLP_PROTOCOL` - A base protocol to use when sending trace data. Default is `http/protobuf`. Valid values are `http/protobuf`, `grpc`. 68 | - `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` - The protocol to use when sending trace data. Overrides `OTEL_EXPORTER_OTLP_PROTOCOL`. 69 | - `OTEL_SDK_DISABLED` - Disables the SDK if set to `true`. 70 | - `OTEL_SERVICE_NAME` - The name of the service. 71 | 72 | ## Context Propagation 73 | 74 | `Runwasi` uses the `TRACECONTEXT` environment variable to propagate the trace context between the parent shim process and the child. The trace context is a W3C Trace Context header. 75 | -------------------------------------------------------------------------------- /docs/src/resources/community.md: -------------------------------------------------------------------------------- 1 | # Community 2 | 3 | The Runwasi project thrives thanks to its community of contributors and users. We welcome participation from individuals and organizations interested in running WebAssembly workloads in container environments. 4 | 5 | ## Communication Channels 6 | 7 | ### Slack 8 | 9 | - If you haven't joined the CNCF Slack yet, you can do so [here](https://slack.cncf.io/). 10 | - Come join us on our [#runwasi](https://cloud-native.slack.com/archives/C04LTPB6Z0V) channel on the CNCF Slack. 11 | 12 | ### Community Calls 13 | 14 | We hold regular community calls to discuss project updates, upcoming features, and address questions: 15 | 16 | - **Schedule**: Tuesdays every other week at 9:00 AM PT 17 | - **Zoom Link**: [Zoom Meeting](https://zoom.us/my/containerd?pwd=bENmREpnSGRNRXdBZWV5UG8wbU1oUT09) 18 | - **Meeting Notes**: [Google Doc](https://docs.google.com/document/d/1aOJ-O7fgMyRowHD0kOoA2Z_4d19NyAvvdqOkZO3Su_M/edit?usp=sharing) 19 | 20 | These calls are open to everyone interested in the project, regardless of experience level. 21 | 22 | ## Getting Help 23 | 24 | If you need help with Runwasi, there are several ways to get assistance: 25 | 26 | 1. **Slack**: Ask questions in the [#runwasi](https://cloud-native.slack.com/archives/C04LTPB6Z0V) channel 27 | 2. **GitHub Issues**: [File an issue](https://github.com/containerd/runwasi/issues) for bugs or feature requests 28 | 3. **Community Calls**: Join our biweekly calls to discuss with the maintainers 29 | 4. **Documentation**: Browse our [documentation](../) for guides and references 30 | 31 | ## Contributing 32 | 33 | We welcome contributions of all kinds! Whether you're fixing a typo, improving documentation, adding a feature, or reporting a bug, your help is appreciated. 34 | 35 | See our [Contributing Guide](https://runwasi.dev/CONTRIBUTING.html) for detailed information. 36 | 37 | ## Projects Using Runwasi 38 | 39 | Runwasi is being used by several projects in the WebAssembly and container ecosystems: 40 | 41 | - [spinkube/containerd-shim-spin](https://github.com/spinkube/containerd-shim-spin) 42 | - [deislabs/containerd-wasm-shims](https://github.com/deislabs/containerd-wasm-shims) 43 | 44 | If you're using Runwasi in your project, we'd love to hear about it! Let us know on Slack or during a community call. 45 | 46 | ## Code of Conduct 47 | 48 | Runwasi follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). We are committed to providing a welcoming and inclusive environment for all participants. 49 | 50 | ## Roadmap and Future Direction 51 | 52 | To learn about our current development focus and future plans, check out our [Roadmap](../developer/roadmap.md). 53 | -------------------------------------------------------------------------------- /docs/src/resources/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | This page answers common questions about Runwasi. 4 | 5 | [TODO] 6 | 7 | ### Where can I get help if I have more questions? 8 | 9 | If you have more questions, you can: 10 | - Join our [Slack channel](https://cloud-native.slack.com/archives/C04LTPB6Z0V) 11 | - File an issue on [GitHub](https://github.com/containerd/runwasi/issues) 12 | - Attend our community calls (see the [Community page](./community.md) for details) 13 | -------------------------------------------------------------------------------- /docs/src/windows-getting-started.md: -------------------------------------------------------------------------------- 1 | # Windows: Getting Started 2 | 3 | Currently, **runwasi** depends on a Linux environment (i.e., because it has to wire up networking and rootfs mounts). Therefore, to run it on Windows, we recommend utilizing the Windows Subsystem for Linux (WSL). 4 | 5 | To get started with WSL, see [this](https://docs.microsoft.com/en-us/windows/wsl/install). 6 | 7 | Once you have your WSL environment set and you have cloned the **runwasi** repository, you will need to install Docker and the Docker Buildx plugin. 8 | 9 | To install Docker and the Docker Buildx Plugin, see [this](https://docs.docker.com/engine/install/) to find specific installation instructions for your WSL distro. 10 | 11 | Before proceeding, it's also recommended to install Docker Desktop on Windows and run it once. 12 | 13 | To finish off installing pre-requisites, install Rust following [this](https://www.rust-lang.org/tools/install). 14 | 15 | After following these steps and navigating to the runwasi directory in your terminal: 16 | - run `make build`, 17 | - run `make install`, 18 | - run `make pull-app`. 19 | 20 | After this, you can execute an example, like: `ctr run --rm --runtime=io.containerd.wasmtime.v1 ghcr.io/containerd/runwasi/wasi-demo-app:latest testwasm`. 21 | 22 | > To kill the process from the example, you can run: `ctr task kill -s SIGKILL testwasm`. 23 | 24 | ## Building and developing on Windows 25 | 26 | You need to install `wasmedge`, `llvm` and `make`. This can be done using `winget`, `choco` or manually. (note as of writing this `winget` doesn't have the latest package and will builds will fail). See `.github/scripts/build-windows.sh` for an example. 27 | 28 | Once you have those dependencies you will need to set env: 29 | 30 | ``` 31 | $env:WASMEDGE_LIB_DIR="C:\Program Files\WasmEdge\lib" 32 | $env:WASMEDGE_INCLUDE_DIR="C:\Program Files\WasmEdge\include" 33 | ``` 34 | 35 | Then you can run: 36 | 37 | ``` 38 | make build 39 | ``` 40 | 41 | ### Using VS code 42 | If you are using VS Code for development you can use the following `settings.json` in the `.vscode` folder of the project: 43 | 44 | ``` 45 | { 46 | "rust-analyzer.cargo.noDefaultFeatures": true, 47 | "rust-analyzer.cargo.extraEnv": { 48 | "WASMEDGE_LIB_DIR": "C:\\Program Files\\WasmEdge\\lib", 49 | "WASMEDGE_INCLUDE_DIR": "C:\\Program Files\\WasmEdge\\include" 50 | } 51 | } 52 | ``` -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel="1.85.0" 3 | profile="default" 4 | targets = ["wasm32-wasip1"] 5 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | newline_style = "Native" 2 | unstable_features = true # Cargo fmt now needs to be called with `cargo +nightly fmt` 3 | group_imports = "StdExternalCrate" # create three groups for std, external and local crates 4 | # Merge imports from the same module 5 | # See: https://rust-lang.github.io/rustfmt/?version=v1.4.38&search=#imports_granularity 6 | imports_granularity = "Module" -------------------------------------------------------------------------------- /scripts/benchmark-mem.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | # Parse CLI arguments 6 | RUNTIME=${1:-"wasmtime"}; shift || true 7 | IMAGE=${1:-"ghcr.io/containerd/runwasi/wasi-demo-app:latest"}; shift || true 8 | 9 | if [ $IMAGE == "ghcr.io/containerd/runwasi/wasi-demo-app:latest" ] && [ "$#" == "0" ]; then 10 | set -- /wasi-demo-app.wasm echo 'hello' 11 | fi 12 | 13 | # Run the shim and collect logs 14 | LOG_FILE=$(mktemp) 15 | journalctl -fn 0 -u containerd | timeout -k 16s 15s grep -m 2 'peak memory usage was' > $LOG_FILE & 16 | ctr run --null-io --rm --runtime=io.containerd.$RUNTIME.v1 "$IMAGE" testwasm "$@" 17 | 18 | # Parse the logs 19 | wait 20 | SHIM_MEM=$(cat $LOG_FILE | grep 'Shim peak memory usage was' | sed -E 's/.*peak resident set ([0-9]+) kB.*/\1/') 21 | ZYGOTE_MEM=$(cat $LOG_FILE | grep 'Zygote peak memory usage was' | sed -E 's/.*peak resident set ([0-9]+) kB.*/\1/') 22 | rm $LOG_FILE 23 | 24 | if [ "$SHIM_MEM" == "" ] || [ "$ZYGOTE_MEM" == "" ]; then 25 | exit 1 26 | fi 27 | 28 | TOTAL_MEM=$(( $SHIM_MEM + $ZYGOTE_MEM )) 29 | 30 | # Print the JSON for the benchmark report 31 | cat < 0 then add | sort else . end' 14 | -------------------------------------------------------------------------------- /scripts/cleanup-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Cleanup contianerd content cache and leases after a failure in the containerd-client tests 4 | 5 | # The containerd-client tests interact with the real containerd in the computer, creating leases and caching content in different namespaces. 6 | # If a containerd-client tests is interrupted (by either a test failure during development, or by the user with ctrl-c), containerd is 7 | # left polluted with a bunch of leases and cached content. 8 | # This can lead to subsequent test runs failing due to the pre-existing leases being present. 9 | 10 | # This cleans-up any remaining leases and cached content that the test might have left behind 11 | 12 | function cleanup() { 13 | IDS="$(ctr --namespace $1 $2 ls | tail -n +2 | awk '{print $1}')" 14 | if [ "$IDS" != "" ]; then 15 | echo $IDS 16 | ctr --namespace $1 $2 rm $IDS 17 | fi 18 | } 19 | 20 | cleanup runwasi-test leases 21 | cleanup runwasi-test content 22 | cleanup test-ns leases 23 | cleanup test-ns content 24 | cleanup test leases 25 | cleanup test content 26 | -------------------------------------------------------------------------------- /scripts/crates.jq: -------------------------------------------------------------------------------- 1 | def filter_by_package(package): if package != "" then map(select(.name == package)) else . end; 2 | 3 | def get_bins: map(.targets | map(select(.kind[] | contains("bin")).name))[] | select(length > 0); 4 | -------------------------------------------------------------------------------- /scripts/extract-changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Inspired by https://stackoverflow.com/questions/40450238/parse-a-changelog-and-extract-changes-for-a-version 3 | # This script will extract the changelog for a specific version from the CHANGELOG.md file 4 | # Usage: ./extract-changelog.sh 5 | version=$1 6 | 7 | awk -v ver="$version" ' 8 | /^## \[.*\]/ { 9 | if (p) exit 10 | if ($0 ~ "^## \\[" ver "\\]") { p=1; next } 11 | } 12 | p' crates/containerd-shim-wasm/CHANGELOG.md -------------------------------------------------------------------------------- /scripts/parse-hey.py: -------------------------------------------------------------------------------- 1 | # Usage: 2 | # 3 | # First run: hey http://127.0.0.1:8080 > raw-output.txt 4 | # Then run: python ./scripts/parse-hey.py ./raw-output.txt 5 | # Output: 6 | # [ 7 | # { 8 | # "name": "HTTP RPS", 9 | # "unit": "req/s", 10 | # "value": 13334.5075 11 | # }, 12 | # { 13 | # "name": "HTTP p95 Latency", 14 | # "unit": "ms", 15 | # "value": 4.1000000000000005 16 | # } 17 | # ] 18 | 19 | 20 | import json 21 | import re 22 | import sys 23 | 24 | def parse_hey_output(file_path): 25 | with open(file_path, 'r') as f: 26 | lines = f.readlines() 27 | 28 | rps = None 29 | lat = None 30 | 31 | for i, line in enumerate(lines): 32 | line = line.strip() 33 | 34 | if line.startswith("Requests/sec:"): 35 | parts = line.split() 36 | if len(parts) >= 2: 37 | rps = float(parts[1]) 38 | 39 | if "Latency distribution:" in line: 40 | for j in range(i+1, len(lines)): 41 | if "% in" in lines[j] and "95%" in lines[j]: 42 | match = re.search(r'95% in ([0-9.]+) secs', lines[j]) 43 | if match: 44 | lat = float(match.group(1)) 45 | break 46 | 47 | return rps, lat 48 | 49 | 50 | if __name__ == "__main__": 51 | if len(sys.argv) != 2: 52 | print("Usage: python parse_hey.py ") 53 | sys.exit(1) 54 | 55 | file_path = sys.argv[1] 56 | rps, latency = parse_hey_output(file_path) 57 | 58 | latency = latency * 1000 if latency is not None else None 59 | 60 | if rps is not None: 61 | throughput_result = [{ 62 | "name": "HTTP RPS", 63 | "unit": "req/s", 64 | "value": rps 65 | }] 66 | with open('throughput_results.json', 'w') as f: 67 | json.dump(throughput_result, f, indent=2) 68 | 69 | if latency is not None: 70 | latency_result = [{ 71 | "name": "HTTP p95 Latency", 72 | "unit": "ms", 73 | "value": latency 74 | }] 75 | with open('latency_results.json', 'w') as f: 76 | json.dump(latency_result, f, indent=2) 77 | -------------------------------------------------------------------------------- /scripts/setup-cross.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cargo install cross --git https://github.com/cross-rs/cross 3 | 4 | if [ ! -z "$CI" ]; then 5 | 6 | echo "CARGO=cross" >> ${GITHUB_ENV} 7 | 8 | # See https://github.com/containerd/runwasi/pull/813#issuecomment-2619138618 9 | echo "CROSS_NO_WARNINGS=0" >> ${GITHUB_ENV} 10 | 11 | if [ ! -z "$1" ]; then 12 | echo "TARGET=$1" >> ${GITHUB_ENV} 13 | fi 14 | 15 | fi 16 | -------------------------------------------------------------------------------- /scripts/setup-jeager-and-otel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | # start jeager endpoint 5 | docker run -d -p16686:16686 -p4317:4317 -p4318:4318 -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:latest 6 | 7 | systemctl stop containerd 8 | 9 | mkdir -p /etc/systemd/system/containerd.service.d 10 | 11 | # Add the environment variable to the override file 12 | cat < /etc/systemd/system/containerd.service.d/override.conf 13 | [Service] 14 | Environment="OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318" 15 | Environment="OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf" 16 | Environment="OTEL_SERVICE_NAME=containerd" 17 | EOF 18 | 19 | systemctl daemon-reload 20 | systemctl restart containerd -------------------------------------------------------------------------------- /scripts/setup-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo apt -y update 3 | sudo apt install -y pkg-config libsystemd-dev libdbus-glib-1-dev build-essential libelf-dev libseccomp-dev libclang-dev libzstd-dev protobuf-compiler libssl-dev 4 | 5 | if [ ! -z "$CI" ] && ! mount | grep cgroup; then 6 | echo "cgroup is not mounted" 1>&2 7 | exit 1 8 | fi -------------------------------------------------------------------------------- /scripts/setup-windows.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # when we bump wasmedge-sdk version, we may need to update the version here as well 3 | choco install -y wasmedge --version 0.14.1 4 | # require clang for wasmedge for bindgen, which is used in the build script to generate the rust bindings to the c codebase 5 | choco install -y llvm --version 16.0.6 6 | choco install -y protoc 7 | 8 | if [ ! -z "$CI" ]; then 9 | echo "WASMEDGE_LIB_DIR=C:\Program Files\WasmEdge\lib" >> ${GITHUB_ENV} 10 | echo "WASMEDGE_INCLUDE_DIR=C:\Program Files\WasmEdge\include" >> ${GITHUB_ENV} 11 | fi -------------------------------------------------------------------------------- /scripts/test-runner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo $@ 3 | TARGET_DIR="$(dirname $0)/.." 4 | WASMEDGE_PATH=$(dirname $(find "$TARGET_DIR" -name libwasmedge.so | head -n 1) 2>/dev/null || echo "") 5 | sudo -E env LD_LIBRARY_PATH="${WASMEDGE_PATH}" "$@" -------------------------------------------------------------------------------- /scripts/verify-jaeger-traces.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | TRACE_DATA=$(curl -s "http://localhost:16686/api/traces?service=containerd&limit=0" \ 5 | | jq '[ .data[].spans[].operationName ]') 6 | 7 | PREFIX="containerd_shimkit::sandbox" 8 | REQUIRED_OPS=( 9 | "${PREFIX}::shim::local::create" 10 | "${PREFIX}::shim::local::wait" 11 | "${PREFIX}::shim::local::start" 12 | "${PREFIX}::shim::local::delete" 13 | "${PREFIX}::shim::local::state" 14 | "${PREFIX}::shim::local::shutdown" 15 | "${PREFIX}::cli::shim_main_inner" 16 | ) 17 | 18 | for op in "${REQUIRED_OPS[@]}"; do 19 | COUNT=$(echo "$TRACE_DATA" | jq --arg op "$op" '[ .[] | select(. == $op) ] | length') 20 | if [ "$COUNT" -eq 0 ]; then 21 | echo "Operation '$op' not found in Jaeger!" 22 | exit 1 23 | fi 24 | done 25 | 26 | echo "All required operations found in Jaeger!" -------------------------------------------------------------------------------- /scripts/version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Get the list of binaries from the Cargo.toml file. 4 | # If targeting a specific crate, pass the crate name as the first argument. 5 | 6 | read -r -d '' Q <<-'EOF' 7 | include "crates"; 8 | .packages | filter_by_package($CRATE)[0].version 9 | EOF 10 | 11 | set -u -e -o pipefail 12 | 13 | cargo metadata --format-version=1 --no-deps | jq -r -L "${BASH_SOURCE[0]%/*}" --arg CRATE "${1}" "${Q}" 14 | -------------------------------------------------------------------------------- /test/k3s/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | rm -f /var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl 5 | cp -f /var/lib/rancher/k3s/agent/etc/containerd/config.toml /var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl 6 | cat <> /var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl 7 | [plugins."io.containerd.cri.v1.runtime".containerd.runtimes.wasm] 8 | runtime_type = "$PWD/dist/bin/containerd-shim-$1-v1" 9 | [plugins."io.containerd.cri.v1.runtime".containerd.runtimes.wasm.options] 10 | SystemdCgroup = true 11 | EOF 12 | 13 | cat < /etc/systemd/system/k3s-runwasi.service.env 14 | CONTAINERD_NAMESPACE='${CONTAINERD_NAMESPACE:-default}' 15 | NO_PROXY=192.168.0.0/16 16 | EOF 17 | 18 | systemctl daemon-reload 19 | systemctl restart k3s-runwasi 20 | while ! bin/k3s ctr version; do sleep 1; done 21 | while [ "$(bin/k3s kubectl get pods --all-namespaces --no-headers | wc -l)" == "0" ]; do sleep 1; done 22 | while [ "$(bin/k3s kubectl get pods --all-namespaces --no-headers | grep -vE "Completed|Running" | wc -l)" != "0" ]; do sleep 1; done 23 | -------------------------------------------------------------------------------- /test/k8s/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.4 2 | 3 | ARG KIND_VERSION=v0.21.0 4 | ARG KIND_NODE_VERSION=v1.29.1@sha256:a0cc28af37cf39b019e2b448c54d1a3f789de32536cb5a5db61a49623e527144 5 | ARG RUNTIME=wasmtime 6 | 7 | FROM scratch AS kind 8 | ARG TARGETARCH KIND_VERSION 9 | ADD --chmod=755 https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-${TARGETARCH} /kind 10 | 11 | FROM kindest/node:${KIND_NODE_VERSION} 12 | RUN apt-get update -y && \ 13 | apt-get install --no-install-recommends -y dbus 14 | 15 | ADD dist/bin/* /usr/local/bin/ 16 | 17 | ARG RUNTIME 18 | RUN cat <> /etc/containerd/config.toml 19 | [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasm] 20 | runtime_type = "io.containerd.${RUNTIME}.v1" 21 | [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasm.options] 22 | SystemdCgroup = true 23 | EOF 24 | -------------------------------------------------------------------------------- /test/k8s/deploy.oci.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: node.k8s.io/v1 2 | kind: RuntimeClass 3 | metadata: 4 | name: wasm 5 | handler: wasm 6 | --- 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: wasi-demo 11 | labels: 12 | app: wasi-demo 13 | spec: 14 | replicas: 3 15 | selector: 16 | matchLabels: 17 | app: wasi-demo 18 | template: 19 | metadata: 20 | labels: 21 | app: wasi-demo 22 | spec: 23 | runtimeClassName: wasm 24 | containers: 25 | - name: demo 26 | image: ghcr.io/containerd/runwasi/wasi-demo-oci:latest 27 | - name: demo-artifact 28 | image: ghcr.io/containerd/runwasi/wasi-demo-oci-artifact:latest 29 | command: ["wasi-demo.wasm"] 30 | - name: nginx 31 | image: docker.io/nginx:latest 32 | ports: 33 | - containerPort: 80 34 | -------------------------------------------------------------------------------- /test/k8s/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: node.k8s.io/v1 2 | kind: RuntimeClass 3 | metadata: 4 | name: wasm 5 | handler: wasm 6 | --- 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: wasi-demo 11 | labels: 12 | app: wasi-demo 13 | spec: 14 | replicas: 3 15 | selector: 16 | matchLabels: 17 | app: wasi-demo 18 | template: 19 | metadata: 20 | labels: 21 | app: wasi-demo 22 | spec: 23 | runtimeClassName: wasm 24 | containers: 25 | - name: demo 26 | image: ghcr.io/containerd/runwasi/wasi-demo-app:latest 27 | - name: nginx 28 | image: docker.io/nginx:latest 29 | ports: 30 | - containerPort: 80 31 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | # allow disabling the next line, e.g: 3 | # self.namespaces 4 | # // typos:disable-next-line - false positive "typ" 5 | # .retain(|ns| ns.typ() != LinuxNamespaceType::Network); 6 | extend-ignore-re = [ 7 | "(?Rm)^\\s*//\\s*typos:disable-next-line(\\s.*|\\s*)(\\r|\\n)+[^\\r\\n]*$", 8 | ] 9 | 10 | [files] 11 | extend-exclude = ["docs/mermaid.min.js"] --------------------------------------------------------------------------------