mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-04-29 20:24:31 +00:00
agent-ctl: Update for Hybrid VSOCK
Allow the `agent-ctl` tool to connect to a Hybrid VSOCK hypervisor such as Cloud Hypervisor or Firecracker. Fixes: #2914. Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
This commit is contained in:
parent
d1bcf105ff
commit
82de838e5f
@ -19,6 +19,7 @@ vendor:
|
||||
test:
|
||||
|
||||
install:
|
||||
@RUSTFLAGS="$(EXTRA_RUSTFLAGS) --deny warnings" cargo install --target $(TRIPLE) --path .
|
||||
|
||||
check:
|
||||
|
||||
|
@ -45,7 +45,7 @@ the agent protocol and the client and server implementations.
|
||||
| Agent Control (client) API calls | [`src/client.rs`](src/client.rs) | `agent_cmd_container_create()` | Agent Control tool function that calls the `CreateContainer` API. |
|
||||
| Agent (server) API implementations | [`rpc.rs`](../../src/agent/src/rpc.rs) | `create_container()` | Server function that implements the `CreateContainers` API. |
|
||||
|
||||
## Running the tool
|
||||
## Run the tool
|
||||
|
||||
### Prerequisites
|
||||
|
||||
@ -62,22 +62,31 @@ $ sudo docker export $(sudo docker create "$image") | tar -C "$rootfs_dir" -xvf
|
||||
|
||||
### Connect to a real Kata Container
|
||||
|
||||
The method used to connect to Kata Containers agent depends on the configured
|
||||
hypervisor. Although by default the Kata Containers agent listens for API calls on a
|
||||
VSOCK socket, the way that socket is exposed to the host depends on the
|
||||
hypervisor.
|
||||
|
||||
#### QEMU
|
||||
|
||||
Since QEMU supports VSOCK sockets in the standard way, it is only necessary to
|
||||
establish the VSOCK guest CID value to connect to the agent.
|
||||
|
||||
1. Start a Kata Container
|
||||
|
||||
1. Establish the VSOCK guest CID number for the virtual machine:
|
||||
|
||||
Assuming you are running a single QEMU based Kata Container, you can look
|
||||
at the program arguments to find the (randomly-generated) `guest-cid=` option
|
||||
value:
|
||||
|
||||
```sh
|
||||
$ guest_cid=$(ps -ef | grep qemu-system-x86_64 | egrep -o "guest-cid=[0-9]*" | cut -d= -f2)
|
||||
$ guest_cid=$(sudo ss -H --vsock | awk '{print $6}' | cut -d: -f1)
|
||||
```
|
||||
|
||||
1. Run the tool to connect to the agent:
|
||||
|
||||
```sh
|
||||
$ cargo run -- -l debug connect --bundle-dir "${bundle_dir}" --server-address "vsock://${guest_cid}:1024" -c Check -c GetGuestDetails
|
||||
# Default VSOCK port the agent listens on
|
||||
$ agent_vsock_port=1024
|
||||
|
||||
$ cargo run -- -l debug connect --bundle-dir "${bundle_dir}" --server-address "vsock://${guest_cid}:${agent_vsock_port}" -c Check -c GetGuestDetails
|
||||
```
|
||||
|
||||
This examples makes two API calls:
|
||||
@ -86,6 +95,75 @@ $ sudo docker export $(sudo docker create "$image") | tar -C "$rootfs_dir" -xvf
|
||||
- It then runs `GetGuestDetails` to establish some details of the
|
||||
environment the agent is running in.
|
||||
|
||||
#### Cloud Hypervisor and Firecracker
|
||||
|
||||
Cloud Hypervisor and Firecracker both use "hybrid VSOCK" which uses a local
|
||||
UNIX socket rather than the host kernel to handle communication with the
|
||||
guest. As such, you need to specify the path to the UNIX socket.
|
||||
|
||||
Since the UNIX socket path is sandbox-specific, you need to run the
|
||||
`kata-runtime env` command to determine the socket's "template path". This
|
||||
path includes a `{ID}` tag that represents the real sandbox ID or name.
|
||||
|
||||
Further, since the socket path is below the sandbox directory and since that
|
||||
directory is `root` owned, it is necessary to run the tool as `root` when
|
||||
using a Hybrid VSOCKS hypervisor.
|
||||
|
||||
##### Determine socket path template value
|
||||
|
||||
###### Configured hypervisor is Cloud Hypervisor
|
||||
|
||||
```bash
|
||||
$ socket_path_template=$(sudo kata-runtime env --json | jq '.Hypervisor.SocketPath')
|
||||
$ echo "$socket_path_template"
|
||||
"/run/vc/vm/{ID}/clh.sock"
|
||||
```
|
||||
|
||||
###### Configured hypervisor is Firecracker
|
||||
|
||||
```bash
|
||||
$ socket_path_template=$(sudo kata-runtime env --json | jq '.Hypervisor.SocketPath')
|
||||
$ echo "$socket_path_template"
|
||||
"/run/vc/firecracker/{ID}/root/kata.hvsock"
|
||||
```
|
||||
|
||||
> **Note:**
|
||||
>
|
||||
> Do not rely on the paths shown above: you should run the command yourself
|
||||
> as these paths _may_ change.
|
||||
|
||||
Once you have determined the template path, build and install the tool to make
|
||||
it easier to run as the `root` user.
|
||||
|
||||
##### Build and install
|
||||
|
||||
```bash
|
||||
# Install for user
|
||||
$ make install
|
||||
|
||||
# Install centrally
|
||||
$ sudo install -o root -g root -m 0755 ~/.cargo/bin/kata-agent-ctl /usr/local/bin
|
||||
```
|
||||
|
||||
1. Start a Kata Container
|
||||
|
||||
Create a container called `foo`.
|
||||
|
||||
1. Run the tool
|
||||
|
||||
```bash
|
||||
# Name of container
|
||||
$ sandbox_id="foo"
|
||||
|
||||
# Create actual socket path
|
||||
$ socket_path=$(echo "$socket_path_template" | sed "s/{ID}/${sandbox_id}/g" | tr -d '"')
|
||||
|
||||
$ sudo kata-agent-ctl -l debug connect --bundle-dir "${bundle_dir}" --server-address "unix://${socket_path}" --hybrid-vsock -c Check -c GetGuestDetails
|
||||
```
|
||||
|
||||
> **Note:** The `socket_path_template` variable was set in the
|
||||
> [Determine socket path template value](#determine-socket-path-template-value) section.
|
||||
|
||||
### Run the tool and the agent in the same environment
|
||||
|
||||
> **Warnings:**
|
||||
@ -100,6 +178,8 @@ $ sudo docker export $(sudo docker create "$image") | tar -C "$rootfs_dir" -xvf
|
||||
$ sudo KATA_AGENT_SERVER_ADDR=unix:///tmp/foo.socket target/x86_64-unknown-linux-musl/release/kata-agent
|
||||
```
|
||||
|
||||
> **Note:** This example assumes an Intel x86-64 system.
|
||||
|
||||
1. Run the tool in the same environment:
|
||||
|
||||
```sh
|
||||
|
@ -17,6 +17,7 @@ use protocols::health_ttrpc::*;
|
||||
use slog::{debug, info};
|
||||
use std::io;
|
||||
use std::io::Write; // XXX: for flush()
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::os::unix::io::{IntoRawFd, RawFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::thread::sleep;
|
||||
@ -87,11 +88,6 @@ static AGENT_CMDS: &'static [AgentCmd] = &[
|
||||
st: ServiceType::Agent,
|
||||
fp: agent_cmd_sandbox_add_arp_neighbors,
|
||||
},
|
||||
AgentCmd {
|
||||
name: "AddSwap",
|
||||
st: ServiceType::Agent,
|
||||
fp: agent_cmd_sandbox_add_swap,
|
||||
},
|
||||
AgentCmd {
|
||||
name: "Check",
|
||||
st: ServiceType::Health,
|
||||
@ -372,7 +368,59 @@ fn client_create_vsock_fd(cid: libc::c_uint, port: u32) -> Result<RawFd> {
|
||||
Ok(fd)
|
||||
}
|
||||
|
||||
fn create_ttrpc_client(server_address: String) -> Result<ttrpc::Client> {
|
||||
// Setup the existing stream by making a Hybrid VSOCK host-initiated
|
||||
// connection request to the Hybrid VSOCK-capable hypervisor (CLH or FC),
|
||||
// asking it to route the connection to the Kata Agent running inside the VM.
|
||||
fn setup_hybrid_vsock(mut stream: &UnixStream, hybrid_vsock_port: u64) -> Result<()> {
|
||||
// Challenge message sent to the Hybrid VSOCK capable hypervisor asking
|
||||
// for a connection to a real VSOCK server running in the VM on the
|
||||
// port specified as part of this message.
|
||||
const CONNECT_CMD: &str = "CONNECT";
|
||||
|
||||
// Expected response message returned by the Hybrid VSOCK capable
|
||||
// hypervisor informing the client that the CONNECT_CMD was successful.
|
||||
const OK_CMD: &str = "OK";
|
||||
|
||||
// Contact the agent by dialing it's port number and
|
||||
// waiting for the hybrid vsock hypervisor to route the call for us ;)
|
||||
//
|
||||
// See: https://github.com/firecracker-microvm/firecracker/blob/main/docs/vsock.md#host-initiated-connections
|
||||
let msg = format!("{} {}\n", CONNECT_CMD, hybrid_vsock_port);
|
||||
|
||||
stream.write_all(msg.as_bytes())?;
|
||||
|
||||
// Now, see if we get the expected response
|
||||
let stream_reader = stream.try_clone()?;
|
||||
let mut reader = BufReader::new(&stream_reader);
|
||||
|
||||
let mut msg = String::new();
|
||||
reader.read_line(&mut msg)?;
|
||||
|
||||
if msg.starts_with(OK_CMD) {
|
||||
let response = msg
|
||||
.strip_prefix(OK_CMD)
|
||||
.ok_or(format!("invalid response: {:?}", msg))
|
||||
.map_err(|e| anyhow!(e))?
|
||||
.trim();
|
||||
|
||||
debug!(sl!(), "Hybrid VSOCK host-side port: {:?}", response);
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"failed to setup Hybrid VSOCK connection: response was: {:?}",
|
||||
msg
|
||||
));
|
||||
}
|
||||
|
||||
// The Unix stream is now connected directly to the VSOCK socket
|
||||
// the Kata agent is listening to in the VM.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_ttrpc_client(
|
||||
server_address: String,
|
||||
hybrid_vsock_port: u64,
|
||||
hybrid_vsock: bool,
|
||||
) -> Result<ttrpc::Client> {
|
||||
if server_address == "" {
|
||||
return Err(anyhow!("server address cannot be blank"));
|
||||
}
|
||||
@ -388,7 +436,7 @@ fn create_ttrpc_client(server_address: String) -> Result<ttrpc::Client> {
|
||||
let fd: RawFd = match scheme.as_str() {
|
||||
// Formats:
|
||||
//
|
||||
// - "unix://absolute-path" (domain socket)
|
||||
// - "unix://absolute-path" (domain socket, or hybrid vsock!)
|
||||
// (example: "unix:///tmp/domain.socket")
|
||||
//
|
||||
// - "unix://@absolute-path" (abstract socket)
|
||||
@ -445,6 +493,10 @@ fn create_ttrpc_client(server_address: String) -> Result<ttrpc::Client> {
|
||||
}
|
||||
};
|
||||
|
||||
if hybrid_vsock {
|
||||
setup_hybrid_vsock(&stream, hybrid_vsock_port)?
|
||||
}
|
||||
|
||||
stream.into_raw_fd()
|
||||
}
|
||||
}
|
||||
@ -481,14 +533,22 @@ fn create_ttrpc_client(server_address: String) -> Result<ttrpc::Client> {
|
||||
Ok(ttrpc::client::Client::new(fd))
|
||||
}
|
||||
|
||||
fn kata_service_agent(server_address: String) -> Result<AgentServiceClient> {
|
||||
let ttrpc_client = create_ttrpc_client(server_address)?;
|
||||
fn kata_service_agent(
|
||||
server_address: String,
|
||||
hybrid_vsock_port: u64,
|
||||
hybrid_vsock: bool,
|
||||
) -> Result<AgentServiceClient> {
|
||||
let ttrpc_client = create_ttrpc_client(server_address, hybrid_vsock_port, hybrid_vsock)?;
|
||||
|
||||
Ok(AgentServiceClient::new(ttrpc_client))
|
||||
}
|
||||
|
||||
fn kata_service_health(server_address: String) -> Result<HealthClient> {
|
||||
let ttrpc_client = create_ttrpc_client(server_address)?;
|
||||
fn kata_service_health(
|
||||
server_address: String,
|
||||
hybrid_vsock_port: u64,
|
||||
hybrid_vsock: bool,
|
||||
) -> Result<HealthClient> {
|
||||
let ttrpc_client = create_ttrpc_client(server_address, hybrid_vsock_port, hybrid_vsock)?;
|
||||
|
||||
Ok(HealthClient::new(ttrpc_client))
|
||||
}
|
||||
@ -522,8 +582,17 @@ pub fn client(cfg: &Config, commands: Vec<&str>) -> Result<()> {
|
||||
|
||||
// Create separate connections for each of the services provided
|
||||
// by the agent.
|
||||
let client = kata_service_agent(cfg.server_address.clone())?;
|
||||
let health = kata_service_health(cfg.server_address.clone())?;
|
||||
let client = kata_service_agent(
|
||||
cfg.server_address.clone(),
|
||||
cfg.hybrid_vsock_port,
|
||||
cfg.hybrid_vsock,
|
||||
)?;
|
||||
|
||||
let health = kata_service_health(
|
||||
cfg.server_address.clone(),
|
||||
cfg.hybrid_vsock_port,
|
||||
cfg.hybrid_vsock,
|
||||
)?;
|
||||
|
||||
let mut options = Options::new();
|
||||
|
||||
@ -1923,29 +1992,3 @@ fn get_repeat_count(cmdline: &str) -> i64 {
|
||||
Err(_) => return default_repeat_count,
|
||||
}
|
||||
}
|
||||
|
||||
fn agent_cmd_sandbox_add_swap(
|
||||
ctx: &Context,
|
||||
client: &AgentServiceClient,
|
||||
_health: &HealthClient,
|
||||
_options: &mut Options,
|
||||
_args: &str,
|
||||
) -> Result<()> {
|
||||
let req = AddSwapRequest::default();
|
||||
|
||||
let ctx = clone_context(ctx);
|
||||
|
||||
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
|
||||
|
||||
let reply = client
|
||||
.add_swap(ctx, &req)
|
||||
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
|
||||
|
||||
// FIXME: Implement 'AddSwap' fully.
|
||||
eprintln!("FIXME: 'AddSwap' not fully implemented");
|
||||
|
||||
info!(sl!(), "response received";
|
||||
"response" => format!("{:?}", reply));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use crate::types::Config;
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::{crate_name, crate_version, App, Arg, SubCommand};
|
||||
use std::io;
|
||||
@ -39,6 +40,9 @@ const WARNING_TEXT: &str = r#"WARNING:
|
||||
irrevocably other parts of the system or even kill a running container or
|
||||
sandbox."#;
|
||||
|
||||
// The VSOCK port number the Kata agent uses to listen to API requests on.
|
||||
const DEFAULT_KATA_AGENT_API_VSOCK_PORT: &str = "1024";
|
||||
|
||||
fn make_examples_text(program_name: &str) -> String {
|
||||
let abstract_server_address = "unix://@/foo/bar/abstract.socket";
|
||||
let bundle = "$bundle_dir";
|
||||
@ -47,6 +51,7 @@ fn make_examples_text(program_name: &str) -> String {
|
||||
let local_server_address = "unix:///tmp/local.socket";
|
||||
let sandbox_id = "$sandbox_id";
|
||||
let vsock_server_address = "vsock://3:1024";
|
||||
let hybrid_vsock_server_address = "unix:///run/vc/vm/foo/clh.sock";
|
||||
|
||||
format!(
|
||||
r#"EXAMPLES:
|
||||
@ -55,6 +60,10 @@ fn make_examples_text(program_name: &str) -> String {
|
||||
|
||||
$ {program} connect --server-address "{vsock_server_address}" --cmd Check
|
||||
|
||||
- Connect to the agent using a Hybrid VSOCK hypervisor (here Cloud Hypervisor):
|
||||
|
||||
$ {program} connect --server-address "{hybrid_vsock_server_address}" --hybrid-vsock --cmd Check
|
||||
|
||||
- Connect to the agent using local sockets (when running in same environment as the agent):
|
||||
|
||||
# Local socket
|
||||
@ -109,6 +118,7 @@ fn make_examples_text(program_name: &str) -> String {
|
||||
program = program_name,
|
||||
sandbox_id = sandbox_id,
|
||||
vsock_server_address = vsock_server_address,
|
||||
hybrid_vsock_server_address = hybrid_vsock_server_address,
|
||||
)
|
||||
}
|
||||
|
||||
@ -124,7 +134,8 @@ fn connect(name: &str, global_args: clap::ArgMatches) -> Result<()> {
|
||||
let server_address = args
|
||||
.value_of("server-address")
|
||||
.ok_or("need server adddress".to_string())
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
.map_err(|e| anyhow!(e))?
|
||||
.to_string();
|
||||
|
||||
let mut commands: Vec<&str> = Vec::new();
|
||||
|
||||
@ -149,17 +160,28 @@ fn connect(name: &str, global_args: clap::ArgMatches) -> Result<()> {
|
||||
None => 0,
|
||||
};
|
||||
|
||||
let bundle_dir = args.value_of("bundle-dir").unwrap_or("");
|
||||
let hybrid_vsock_port: u64 = args
|
||||
.value_of("hybrid-vsock-port")
|
||||
.ok_or("Need Hybrid VSOCK port number")
|
||||
.map(|p| p.parse::<u64>().unwrap())
|
||||
.map_err(|e| anyhow!("VSOCK port number must be an integer: {:?}", e))?;
|
||||
|
||||
let result = rpc::run(
|
||||
&logger,
|
||||
let bundle_dir = args.value_of("bundle-dir").unwrap_or("").to_string();
|
||||
|
||||
let hybrid_vsock = args.is_present("hybrid-vsock");
|
||||
|
||||
let cfg = Config {
|
||||
server_address,
|
||||
bundle_dir,
|
||||
interactive,
|
||||
ignore_errors,
|
||||
timeout_nano,
|
||||
commands,
|
||||
);
|
||||
hybrid_vsock_port,
|
||||
hybrid_vsock,
|
||||
};
|
||||
|
||||
let result = rpc::run(&logger, &cfg, commands);
|
||||
|
||||
if result.is_err() {
|
||||
return result;
|
||||
}
|
||||
@ -170,6 +192,11 @@ fn connect(name: &str, global_args: clap::ArgMatches) -> Result<()> {
|
||||
fn real_main() -> Result<()> {
|
||||
let name = crate_name!();
|
||||
|
||||
let hybrid_vsock_port_help = format!(
|
||||
"Kata agent VSOCK port number (only useful with --hybrid-vsock) [default: {}]",
|
||||
DEFAULT_KATA_AGENT_API_VSOCK_PORT
|
||||
);
|
||||
|
||||
let app = App::new(name)
|
||||
.version(crate_version!())
|
||||
.about(ABOUT_TEXT)
|
||||
@ -209,6 +236,19 @@ fn real_main() -> Result<()> {
|
||||
.long("ignore-errors")
|
||||
.help("Don't exit on first error"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("hybrid-vsock")
|
||||
.long("hybrid-vsock")
|
||||
.help("Treat a unix:// server address as a Hybrid VSOCK one"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("hybrid-vsock-port")
|
||||
.long("hybrid-vsock-port")
|
||||
.help(&hybrid_vsock_port_help)
|
||||
.default_value(DEFAULT_KATA_AGENT_API_VSOCK_PORT)
|
||||
.takes_value(true)
|
||||
.value_name("PORT")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("interactive")
|
||||
.short("i")
|
||||
|
@ -11,25 +11,9 @@ use slog::{o, Logger};
|
||||
use crate::client::client;
|
||||
use crate::types::Config;
|
||||
|
||||
pub fn run(
|
||||
logger: &Logger,
|
||||
server_address: &str,
|
||||
bundle_dir: &str,
|
||||
interactive: bool,
|
||||
ignore_errors: bool,
|
||||
timeout_nano: i64,
|
||||
commands: Vec<&str>,
|
||||
) -> Result<()> {
|
||||
let cfg = Config {
|
||||
server_address: server_address.to_string(),
|
||||
bundle_dir: bundle_dir.to_string(),
|
||||
timeout_nano: timeout_nano,
|
||||
interactive: interactive,
|
||||
ignore_errors: ignore_errors,
|
||||
};
|
||||
|
||||
pub fn run(logger: &Logger, cfg: &Config, commands: Vec<&str>) -> Result<()> {
|
||||
// Maintain the global logger for the duration of the ttRPC comms
|
||||
let _guard = slog_scope::set_global_logger(logger.new(o!("subsystem" => "rpc")));
|
||||
|
||||
client(&cfg, commands)
|
||||
client(cfg, commands)
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ pub struct Config {
|
||||
pub server_address: String,
|
||||
pub bundle_dir: String,
|
||||
pub timeout_nano: i64,
|
||||
pub hybrid_vsock_port: u64,
|
||||
pub interactive: bool,
|
||||
pub hybrid_vsock: bool,
|
||||
pub ignore_errors: bool,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user