在 WasmEdge 中使用 Docker 工具管理 WebAssembly 應用

這篇文章將演示如何經過利用 DockerHub 和 CRI-O 等 Docker 工具在 WasmEdge 中部署、管理和運行輕量級 WebAssembly 應用程序。linux

WasmEdgeCNCF (Cloud Native Computing Foundation) 託管的 WebAssembly runtime,是邊緣計算應用程序的執行沙箱。git

雖然 WebAssembly 最初是做爲瀏覽器應用程序的 runtime 而發明的,但其輕量級和高性能的沙箱設計使其成爲通用應用程序容器的一個極具吸引力的選擇。github

若是在 2008 年已經有了 WASM + WASI,那麼咱們壓根無需創始 Docker 這個項目了。 — Docker 聯合創始人 Solomon Hykesdocker

與 Docker 相比, WebAssembly 在啓動時快一百倍, 佔用更小的內存和磁盤空間,而且具備更優定義的安全沙箱。然而,缺點是 WebAssembly 須要本身的語言 SDK 和編譯器工具鏈,使其做爲開發者環境比 Docker 更受限制。WebAssembly 愈來愈多地用於邊緣計算場景,一般這些場景中,部署 Docker 比較困難,或是應用程序的性能相當重要。json

Docker 的一大優點是其豐富的工具生態系統。咱們但願爲 WasmEdge 開發者帶來相似 Docker 的工具。爲了實現這一點,咱們爲 CRI-O 建立了一個名爲 runw 的替代 runner 來加載並運行 WebAssembly 字節碼程序,如同他們是 Docker 鏡像文件同樣。瀏覽器

在 CRI-O 中安裝 WebAssembly runner

爲了在 CRI-O 中支持 WebAssembly,您只需下載 runw 二進制碼發佈並將其安裝到您的 CRI-O 中。安全

由於 runw 二進制碼已經包括了 WasmEdge,無需單獨安裝 WasmEdge 或任何其它 WebAssembly VM。併發

首先,確保你使用的是安裝了 LLVM-10 的 Ubuntu 20.04。若是你使用的是不一樣的平臺,請參閱如何爲你的操做系統建立 runw 項目文檔。app

sudo apt install -y llvm-10-dev liblld-10-dev

確保你安裝了 cri-ocrictlcontainernetworking-pluginsbuildah 或者 dockerdom

下一步,下載 runw binary build

wget https://github.com/second-state/runw/releases/download/0.1.0/runw

如今,你能夠安裝 runw 進 CRI-O 做爲 WebAssembly 的備選方案。

# Get the wasm-pause utility
sudo crictl pull docker.io/beststeve/wasm-pause

# Install runw into cri-o
sudo cp -v runw /usr/lib/cri-o-runc/sbin/runw
sudo chmod +x /usr/lib/cri-o-runc/sbin/runw
sudo sed -i -e 's@default_runtime = "runc"@default_runtime = "runw"@' /etc/crio/crio.conf
sudo sed -i -e 's@pause_image = "k8s.gcr.io/pause:3.2"@pause_image = "docker.io/beststeve/wasm-pause"@' /etc/crio/crio.conf
sudo sed -i -e 's@pause_command = "/pause"@pause_command = "pause.wasm"@' /etc/crio/crio.conf
sudo tee -a /etc/crio/crio.conf.d/01-crio-runc.conf <<EOF
[crio.runtime.runtimes.runw]
runtime_path = "/usr/lib/cri-o-runc/sbin/runw"
runtime_type = "oci"
runtime_root = "/run/runw"
EOF

最後,重啓 cri-o ,從而使新的 WebAssembly runner 開始生效。

sudo systemctl restart crio

用 Rust 構建 Wasm 應用程序

下面案例中的 Wasm 應用程序是 Rust 寫的。爲了讓這些程序工做,確保你安裝了 Rustrustwasmc 工具鏈。

你須要 Rust 編譯器和 rustwasmc 來構建 Rust 源成爲 wasm 字節碼文件。若是你已經有一個 wasm字節碼程序,且只是想要用 cri-o 跑一遍,你能夠跳過這個部分。

應用程序源代碼僅爲一個 main.rs 函數。在此處。該應用程序演示瞭如何使用標準 Rust API 從 WasmEdge 訪問文件系統和其它操做系統資源。

fn main() {
  println!("Random number: {}", get_random_i32());
  println!("Random bytes: {:?}", get_random_bytes());
  println!("{}", echo("This is from a main function"));
  print_env();
  create_file("/tmp.txt", "This is in a file");
  println!("File content is {}", read_file("/tmp.txt"));
  del_file("/tmp.txt");
}

pub fn get_random_i32() -> i32 {
  let x: i32 = random();
  return x;
}

pub fn get_random_bytes() -> Vec<u8> {
  let mut rng = thread_rng();
  let mut arr = [0u8; 128];
  rng.fill(&mut arr[..]);
  return arr.to_vec();
}

pub fn echo(content: &str) -> String {
  println!("Printed from wasi: {}", content);
  return content.to_string();
}

pub fn print_env() {
  println!("The env vars are as follows.");
  for (key, value) in env::vars() {
    println!("{}: {}", key, value);
  }

  println!("The args are as follows.");
  for argument in env::args() {
    println!("{}", argument);
  }
}

pub fn create_file(path: &str, content: &str) {
  let mut output = File::create(path).unwrap();
  output.write_all(content.as_bytes()).unwrap();
}

pub fn read_file(path: &str) -> String {
  let mut f = File::open(path).unwrap();
  let mut s = String::new();
  match f.read_to_string(&mut s) {
    Ok(_) => s,
    Err(e) => e.to_string(),
  }
}

pub fn del_file(path: &str) {
  fs::remove_file(path).expect("Unable to delete");
}

你能夠經過下面的命令行將應用程序構建到一個 wasm 字節碼文件中。

rustwasmc build

wasm 字節碼文件 在這裏。

爲 Wasm app 構建併發佈一個 Docker Hub 鏡像

您如今能夠將整個 wasm 字節碼文件發佈到 Docker hub 中,就好像這是一個 Docker 鏡像同樣。

首先,在 pkg/ 目錄中建立一個 Dockerfile,以下所示。

FROM scratch
ADD wasi_example_main.wasm .
CMD ["wasi_example_main.wasm"]

建立一個鏡像併發布到 Docker hub。

sudo buildah bud -f Dockerfile -t wasm-wasi-example
sudo buildah push wasm-wasi-example docker://registry.example.com/repository:tag

# Example: the following command publishes the wasm image to the public Docker hub under user account "hydai"
sudo buildah push wasm-wasi-example docker://docker.io/hydai/wasm-wasi-example:latest

如今,你可使用 Docker 工具(例如 crictl)將發佈的 wasm 文件拉爲鏡像。 下面是咱們發佈的 wasm 文件鏡像的示例。

sudo crictl pull docker.io/hydai/wasm-wasi-example

使用 CRI-O 啓動 Wasm app

要啓動並運行 wasm 文件,您須要爲 CRI-O 建立兩個配置文件。 建立一個 container_wasi.json 文件,以下所示。 它告訴 CRI-O runtime 應該從 Docker 存儲庫的哪裏提取 wasm 文件映像。

{
  "metadata": {
    "name": "podsandbox1-wasm-wasi"
  },
  "image": {
    "image": "hydai/wasm-wasi-example:latest"
  },
  "args": [
    "wasi_example_main.wasm", "50000000"
  ],
  "working_dir": "/",
  "envs": [],
  "labels": {
    "tier": "backend"
  },
  "annotations": {
    "pod": "podsandbox1"
  },
  "log_path": "",
  "stdin": false,
  "stdin_once": false,
  "tty": false,
  "linux": {
    "resources": {
      "memory_limit_in_bytes": 209715200,
      "cpu_period": 10000,
      "cpu_quota": 20000,
      "cpu_shares": 512,
      "oom_score_adj": 30,
      "cpuset_cpus": "0",
      "cpuset_mems": "0"
    },
    "security_context": {
      "namespace_options": {
        "pid": 1
      },
      "readonly_rootfs": false,
      "capabilities": {
        "add_capabilities": [
          "sys_admin"
        ]
      }
    }
  }
}

接下來,建立一個 sandbox_config.json 文件,以下所示。 它定義了運行 wasm 應用程序的沙箱環境。

{
  "metadata": {
    "name": "podsandbox12",
    "uid": "redhat-test-crio",
    "namespace": "redhat.test.crio",
    "attempt": 1
  },
  "hostname": "crictl_host",
  "log_directory": "",
  "dns_config": {
    "searches": [
      "8.8.8.8"
    ]
  },
  "port_mappings": [],
  "resources": {
    "cpu": {
      "limits": 3,
      "requests": 2
    },
    "memory": {
      "limits": 50000000,
      "requests": 2000000
    }
  },
  "labels": {
    "group": "test"
  },
  "annotations": {
    "owner": "hmeng",
    "security.alpha.kubernetes.io/seccomp/pod": "unconfined"
  },
  "linux": {
    "cgroup_parent": "pod_123-456.slice",
    "security_context": {
      "namespace_options": {
        "network": 0,
        "pid": 1,
        "ipc": 0
      },
      "selinux_options": {
        "user": "system_u",
        "role": "system_r",
        "type": "svirt_lxc_net_t",
        "level": "s0:c4,c5"
      }
    }
  }
}

如今能夠建立一個 CRI-O pod 以下:

# 建立 POD,輸出將會和示例不一樣。
sudo crictl runp sandbox_config.json
7992e75df00cc1cf4bff8bff660718139e3ad973c7180baceb9c84d074b516a4

# 設置一個輔助變量供之後使用。
POD_ID=7992e75df00cc1cf4bff8bff660718139e3ad973c7180baceb9c84d074b516a4

自 pod ,您能夠建立一個容器以隔離方式運行 wasm 字節碼程序。

# 建立容器實例,輸出將會和示例不一樣。
sudo crictl create $POD_ID container_wasi.json sandbox_config.json
1d056e4a8a168f0c76af122d42c98510670255b16242e81f8e8bce8bd3a4476f

最後,啓動容器並查看 wasm 應用程序的輸出。

# 列出容器,狀態應該是 `Created`
sudo crictl ps -a

CONTAINER           IMAGE                           CREATED              STATE               NAME                     ATTEMPT             POD ID
1d056e4a8a168       hydai/wasm-wasi-example:latest   About a minute ago   Created             podsandbox1-wasm-wasi   0                   7992e75df00cc

# 啓動容器
sudo crictl start 1d056e4a8a168f0c76af122d42c98510670255b16242e81f8e8bce8bd3a4476f
1d056e4a8a168f0c76af122d42c98510670255b16242e81f8e8bce8bd3a4476f

# 再次檢查容器狀態。#若是容器沒有完成工做,你會看到運行狀態。 #由於這個例子很小。此時您可能會看到 Exited。
sudo crictl ps -a
CONTAINER           IMAGE                           CREATED              STATE               NAME                     ATTEMPT             POD ID
1d056e4a8a168       hydai/wasm-wasi-example:latest   About a minute ago   Running             podsandbox1-wasm-wasi   0                   7992e75df00cc

# 當容器完成。你能看到狀態變爲 Exited。
sudo crictl ps -a
CONTAINER           IMAGE                           CREATED              STATE               NAME                     ATTEMPT             POD ID
1d056e4a8a168       hydai/wasm-wasi-example:latest   About a minute ago   Exited              podsandbox1-wasm-wasi   0                   7992e75df00cc

# 查看容器記錄 
sudo crictl logs 1d056e4a8a168f0c76af122d42c98510670255b16242e81f8e8bce8bd3a4476f

Test 1: 打印隨機數
Random number: 960251471

Test 2: 打印隨機字節
Random bytes: [50, 222, 62, 128, 120, 26, 64, 42, 210, 137, 176, 90, 60, 24, 183, 56, 150, 35, 209, 211, 141, 146, 2, 61, 215, 167, 194, 1, 15, 44, 156, 27, 179, 23, 241, 138, 71, 32, 173, 159, 180, 21, 198, 197, 247, 80, 35, 75, 245, 31, 6, 246, 23, 54, 9, 192, 3, 103, 72, 186, 39, 182, 248, 80, 146, 70, 244, 28, 166, 197, 17, 42, 109, 245, 83, 35, 106, 130, 233, 143, 90, 78, 155, 29, 230, 34, 58, 49, 234, 230, 145, 119, 83, 44, 111, 57, 164, 82, 120, 183, 194, 201, 133, 106, 3, 73, 164, 155, 224, 218, 73, 31, 54, 28, 124, 2, 38, 253, 114, 222, 217, 202, 59, 138, 155, 71, 178, 113]

Test 3: 調用 echo 函數
Printed from wasi: This is from a main function
This is from a main function

Test 4: 打印環境變量
The env vars are as follows.
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
TERM: xterm
HOSTNAME: crictl_host
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
The args are as follows.
/var/lib/containers/storage/overlay/006e7cf16e82dc7052994232c436991f429109edea14a8437e74f601b5ee1e83/merged/wasi_example_main.wasm
50000000

Test 5: 建立文件 `/tmp.txt` 包含內容 `This is in a file`

Test 6: 從以前文件讀取內容
File content is This is in a file

Test 7: 刪除以前文件

下一步是什麼呢

在本文中,咱們看到了如何使用相似 Docker 的 CRI-O 工具啓動、運行和管理 WasmEdge 應用程序。

咱們的下一步是使用 Kubernetes 來管理 WasmEdge 容器。 爲此,咱們須要在 Kubernetes 中安裝一個 runner 二進制文件,以便它能夠同時支持常規 Docker 鏡像和 wasm 字節碼鏡像。

插播

7月10日,WasmEdge 將亮相 GTOC 開源技術峯會,帶來 《WebAssembly 在雲原生時代的應用》的主題演講,歡迎你們來現場交流!

GOTC 全球開源技術峯會是由開放原子開源基金會與 Linux 基金會聯合開源中國發起的,面向全球開發者的一場盛大開源技術盛宴。大會將攜頭部開源公司和頂級開源項目一塊兒亮相,覆蓋雲原生、大數據、人工智能、物聯網、區塊鏈、DevOps、開源治理等多個技術領域,在爲期 2 天的時間裏,爲開發者帶來全球最新、最純粹的開源技術,同時傳播開源文化和理念,推進開源生態的建設和發展。 報名直戳:https://gotc.oschina.net/

相關文章
相關標籤/搜索