Skip to content

Commit

Permalink
Add --fast-sync flag to control sync behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
talex5 committed Nov 24, 2020
1 parent db79e09 commit adeb21d
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 32 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ Repeating a build will reuse the cached results where possible.
OBuilder aims to be portable, although currently only Linux support is present.
On Linux, it uses `runc` to sandbox the build steps, but any system that can run a command safely in a chroot could be used.

**Note:** OBuilder requires `runc >= v1.0.0-rc92` (otherwise, sync operations will fail with EPERM)

OBuilder stores the log output of each build step.
This is useful for CI, where you may still want to see the output even if the result was cached from some other build.

Expand Down Expand Up @@ -57,6 +55,13 @@ in the context of some container. The store should therefore be configured so
that other processes on the host (which might have the same IDs by coincidence)
cannot reach them, e.g. by `chmod go-rwx /path/to/store`.

Sync operations can be very slow, especially on btrfs. They're also
unnecessary, since if the computer crashes then we'll just discard the whole
build and start again. If you have runc version `v1.0.0-rc92` or later, you can
pass the `--fast-sync` option, which installs a seccomp filter that skips all
sync syscalls. However, if you attempt to use this with an earlier version of
runc then sync operations will instead fail with `EPERM`.

## The build specification language

The spec files are loosly based on the [Dockerfile][] format.
Expand Down
49 changes: 27 additions & 22 deletions lib/runc_sandbox.ml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ let ( / ) = Filename.concat

type t = {
runc_state_dir : string;
fast_sync : bool;
}

module Json_config = struct
Expand Down Expand Up @@ -51,7 +52,28 @@ module Json_config = struct
*)
]

let make {Config.cwd; argv; hostname; user; env; mounts; network} ~config_dir ~results_dir : Yojson.Safe.t =
let seccomp_syscalls ~fast_sync =
if fast_sync then [
`Assoc [
(* Sync calls are pointless for the builder, because if the computer crashes then we'll
just throw the build dir away and start again. And btrfs sync is really slow.
Based on https://bblank.thinkmo.de/using-seccomp-to-filter-sync-operations.html
Note: requires runc >= v1.0.0-rc92. *)
"names", strings [
"fsync";
"fdatasync";
"msync";
"sync";
"syncfs";
"sync_file_range";
];
"action", `String "SCMP_ACT_ERRNO";
"errnoRet", `Int 0; (* Return error "success" *)
];
] else [
]

let make {Config.cwd; argv; hostname; user; env; mounts; network} ~fast_sync ~config_dir ~results_dir : Yojson.Safe.t =
let user =
let { Obuilder_spec.uid; gid } = user in
`Assoc [
Expand Down Expand Up @@ -202,24 +224,7 @@ module Json_config = struct
];
"seccomp", `Assoc [
"defaultAction", `String "SCMP_ACT_ALLOW";
"syscalls", `List [
`Assoc [
(* Sync calls are pointless for the builder, because if the computer crashes then we'll
just throw the build dir away and start again. And btrfs sync is really slow.
Based on https://bblank.thinkmo.de/using-seccomp-to-filter-sync-operations.html
Note: requires runc >= v1.0.0-rc92. *)
"names", strings [
"fsync";
"fdatasync";
"msync";
"sync";
"syncfs";
"sync_file_range";
];
"action", `String "SCMP_ACT_ERRNO";
"errnoRet", `Int 0; (* Return error "success" *)
];
];
"syscalls", `List (seccomp_syscalls ~fast_sync);
];
];
]
Expand All @@ -238,7 +243,7 @@ let copy_to_log ~src ~dst =

let run ~cancelled ?stdin:stdin ~log t config results_dir =
Lwt_io.with_temp_dir ~prefix:"obuilder-runc-" @@ fun tmp ->
let json_config = Json_config.make config ~config_dir:tmp ~results_dir in
let json_config = Json_config.make config ~config_dir:tmp ~results_dir ~fast_sync:(t.fast_sync) in
Os.write_file ~path:(tmp / "config.json") (Yojson.Safe.pretty_to_string json_config ^ "\n") >>= fun () ->
Os.write_file ~path:(tmp / "hosts") "127.0.0.1 localhost builder" >>= fun () ->
let id = string_of_int !next_id in
Expand Down Expand Up @@ -274,6 +279,6 @@ let run ~cancelled ?stdin:stdin ~log t config results_dir =
if Lwt.is_sleeping cancelled then Lwt.return (r :> (unit, [`Msg of string | `Cancelled]) result)
else Lwt_result.fail `Cancelled

let create ~runc_state_dir =
let create ?(fast_sync=false) ~runc_state_dir () =
Os.ensure_dir runc_state_dir;
{ runc_state_dir }
{ runc_state_dir; fast_sync }
8 changes: 7 additions & 1 deletion lib/runc_sandbox.mli
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
include S.SANDBOX

val create : runc_state_dir:string -> t
val create : ?fast_sync:bool -> runc_state_dir:string -> unit -> t
(** [create dir] is a runc sandboxing system that keeps state in [dir].
@param fast_sync Use seccomp to skip all sync syscalls. This is fast (and
safe, since we discard builds after a crash), but requires
runc version 1.0.0-rc92 or later. Note that the runc version
is not the same as the spec version. If "runc --version"
only prints the spec version, then it's too old. *)
17 changes: 12 additions & 5 deletions main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ let log tag msg =
| `Note -> Fmt.pr "%a@." Fmt.(styled (`Fg `Yellow) string) msg
| `Output -> output_string stdout msg; flush stdout

let create_builder spec =
let create_builder ?fast_sync spec =
Obuilder.Store_spec.to_store spec >|= fun (Store ((module Store), store)) ->
let module Builder = Obuilder.Builder(Store)(Sandbox) in
let sandbox = Sandbox.create ~runc_state_dir:(Store.state_dir store / "runc") in
let sandbox = Sandbox.create ~runc_state_dir:(Store.state_dir store / "runc") ?fast_sync () in
let builder = Builder.v ~store ~sandbox in
Builder ((module Builder), builder)

let build store spec src_dir =
let build fast_sync store spec src_dir =
Lwt_main.run begin
create_builder store >>= fun (Builder ((module Builder), builder)) ->
create_builder ~fast_sync store >>= fun (Builder ((module Builder), builder)) ->
let spec = Obuilder.Spec.stage_of_sexp (Sexplib.Sexp.load_sexp spec) in
let context = Obuilder.Context.v ~log ~src_dir () in
Builder.build builder context spec >>= function
Expand Down Expand Up @@ -94,9 +94,16 @@ let id =
~docv:"ID"
[]

let fast_sync =
Arg.value @@
Arg.flag @@
Arg.info
~doc:"Ignore sync syscalls (requires runc >= 1.0.0-rc92)"
["fast-sync"]

let build =
let doc = "Build a spec file." in
Term.(const build $ store $ spec_file $ src_dir),
Term.(const build $ fast_sync $ store $ spec_file $ src_dir),
Term.info "build" ~doc

let delete =
Expand Down
4 changes: 2 additions & 2 deletions stress/stress.ml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ module Test(Store : S.STORE) = struct
| Error `Cancelled -> assert false

let stress_builds store =
let sandbox = Sandbox.create ~runc_state_dir:(Store.state_dir store / "runc") in
let sandbox = Sandbox.create ~runc_state_dir:(Store.state_dir store / "runc") ~fast_sync:true () in
let builder = Build.v ~store ~sandbox in
let pending = ref n_jobs in
let running = ref 0 in
Expand Down Expand Up @@ -194,7 +194,7 @@ module Test(Store : S.STORE) = struct
else Lwt.return_unit

let prune store =
let sandbox = Sandbox.create ~runc_state_dir:(Store.state_dir store / "runc") in
let sandbox = Sandbox.create ~runc_state_dir:(Store.state_dir store / "runc") () in
let builder = Build.v ~store ~sandbox in
let log id = Logs.info (fun f -> f "Deleting %S" id) in
let end_time = Unix.(gettimeofday () +. 60.0 |> gmtime) in
Expand Down

0 comments on commit adeb21d

Please sign in to comment.