XARs: An efficient system for self-contained executables
code.fb.com
code.fb.com
I actually prefer the jar/Tomcat model, where the read-only image gets distributed to servers, and when you run the app the image gets unpacked to disk as needed. You could also write I/O wrappers that would obviate the need to extract them to disk, and you could even make compression optional to reduce performance hits.
It seems like all you really need is a virtual filesystem implemented as a userspace i/o wrapper. Basically FUSE but only for the one app. There's no need for the FUSE kernel shim because only the application is writing to its own virtual filesystem. So this would work on any operating system that supported applications that can overload system calls.
For example, I would start with this project http://avf.sourceforge.net/ and modify it to run apps bundled with itself. With FUSE installed, other apps could interact with its virtual filesystem, but without FUSE, it could still access its own virtual filesystem in an archive. I would then extend it by shimming in a copy-on-write filesystem to stack modifications in a secondary archive.
I always make the same mistake to assume that none would deploy something on windows for a server–side environment.
I think XAR is simpler than the other alternatives; they have more complex specifications, being aimed at distributing full GUI applications (though I bet they work fine for these kinds of use cases, modulo perhaps quick and easy conversion of a Python program into a XAR). I suspect, but haven't measured, that XAR has lower execution overhead since it will re-use a mount point if a tool is recently invoked, rather than remounting every time.
One thing XAR doesn't really try to do is work cross-platform. It will rely on the system Python, for instance, rather than embed the interpreter and all libraries inside (it will embed the libraries your tool depends on that aren't part of Python itself). This is a pro in some cases (lower overhead), but a con if you want something you can carry across wildly different systems.
A big goal for XAR is to be utterly transparent and not require hooks into Python, Node, or other runtimes to function.
To clarify - a stub in each XAR would act as a filesystem driver and intercept calls to open/read/etc, redirecting them to the internal data blob.
Edit: I see your comment below which answers this! https://news.ycombinator.com/item?id=17524910
Could you have solved the same problem ("deploy apps with dependencies in a single file") using snaps? (snapcraft.io)
XAR is much simpler. All it needs is a shell script (or raw executable) to run inside the XAR. Everything else is up to the bootstrapping. Note this isn't always a good thing -- opinionated software has its place. For what we use XAR for, though, having it "just work" with standard Python tooling without expecting open source libraries or modules or build tools behave differently is a strong plus.
Additionally and optionally you can setup a small .desktop file to provide some convenience metadata what will be useful to pack the app easily, and an icon file.
Did you evaluate flatpak/appimage/snap before developing XARs? If so, what were the shortcomings that you noticed in them?
I have found reasonably good success with snap on Python and node apps, but am not an expert on them. Just want to know from other practitioners about any gotchas that others might have studied/stumbled upon.
Thanks!
XAR doesn't aim to, say, represent a sandboxed filesystem or provide some isolation or provide a specification for GUI apps to seamlessly integrate with your desktop. Instead, the main idea is to just get a single file that, when run, mounts a filesystem and runs a command inside it. In my mind, at least, it's a simpler, smaller primitive you could build other systems on top of (like we have with deploying Python, Lua, and Node).
I bet you've worked somewhere with a collision on John. Did you adopt a handle, like John F, or maybe JohnDotAwesome?
R.R.A. Fairley
But I'm a little more into science fiction
# apt install docker
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
docker
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 12.9 kB of archives.
After this operation, 45.1 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu bionic/universe amd64 docker amd64 1.5-1build1 [12.9 kB]
Fetched 12.9 kB in 1s (24.9 kB/s)
Selecting previously unselected package docker.
(Reading database ... 13860 files and directories currently installed.)
Preparing to unpack .../docker_1.5-1build1_amd64.deb ...
Unpacking docker (1.5-1build1) ...
Setting up docker (1.5-1build1) ...
root@testing:~# docker
bash: docker: command not foundIn Debian ecosystem, "docker" is a system tray application: https://icculus.org/openbox/2/docker/
Docker, the container software, is packaged as "docker.io".
My team doesn't handle new user creation, and it's a big company, where sometimes other teams may as well be other companies. I kept getting tickets from this guy and he's reporting that none of his stuff works. There's nothing I can do, but I try to help the guy out.
Anyway, a week later his stuff works. I guess whatever team does the new user creation never planned for there to be more than four employees with the same name, and their automation broke on "johnsmith5". Sigh.
Doesn't seem that big a stretch to imagine.
Companies used to coordinate to make sure they didn't conflict on port numbers, Apple used to run a database for ensuring that creator codes were unique, etc. Now one of the biggest tech companies can't be bothered to check if they collide with another large tech company in a sparsely populated namespace.
The page mentions cryptically "They could almost be thought of as a self-executing container without the virtualization". The "self-executing" bit makes sense - you don't have to remember to type "docker". "without the virtualization" doesn't make sense unless they mean without cgroups or are talking about Kata Containers.
One big benefit is that the filesystem only decompresses the pages as needed, which greatly improves the start up time over existing solutions.
Container isolation (cgroups, namespaces, etc) would make it difficult to do some of the system level tasks we use such tools for, such as configuration changes or monitoring.
Likewise, we often are replacing a PAR or C++ tool with a new XAR version, and it is nice to simply replace the executable and not have to change how it is invoked. In this regard, invoking a XAR is identical to running any normal executable or shell script.
[ed: I now saw this question and answer:
https://news.ycombinator.com/item?id=17526178
Frankly using system/external python (or other VM) seems a bit risky... But whatever works, I guess..]
Containers have virtual file systems they access instead of the host one. XARs don't.
Slightly related: we also recently switched to SquashFS for the gokrazy.org’s root file systems.
If you’re curious about how SquashFS works under the hood, check out https://github.com/gokrazy/internal/blob/master/squashfs/wri.... I also intend to publish a poster about it at some point.
- We had to maintain our own setuid executable to perform the loopback setup and mount (rather than relying on the far more tested and secure open source fusermount setuid binary that all FUSE file systems rely on) - Getting loopback devices to behave inside of containers (generally cgroup and mount namespace containers) was a little tricky at times in some of our environments - We didn't want to have a huge number of extra loopback devices on every host in our fleet
In fact, after implementing the loopback-based filesystem version, we almost abandoned XAR as the downside of the security considerations and in-container behavior wasn't ideal. The open source squashfuse FUSE filesystem really is what made it possible.
Another side benefit is we could iterate far faster with squashfuse -- this let us fix some performance issues, add idle unmounting, and implement zstd-based squashfs files, and then deploy that to our fleet, faster than we could deploy a kernel to 100% of hosts.
On OS-X apps have been distributed in a .app form for ages. It's very uncommon for OS X apps to have installers or a more complicated installation (and uninstallation) than drag and drop.
So, good idea and it kind of fixes a big issue where most linux distributions seem to insist on dll hell with just about anything littering the file system with cruft and just about every interpreter out there reinventing ways to create virtual environments.
Note that Nix users can use `nix-bundle` to create AppImages of all the software in Nixpkgs, which is according to Repology one of the largest and freshest package sets: https://repology.org/statistics
The non-flippant answer is to just provide the source and let the distros package it for you. It's a different model. Linux users want to get their software through an integrated package manager, and volunteers will take your software and do all the work needed to make that happen.
It's not hard to make a static binary.
On the other hand most of the "software" I write - mainly shell scripts - includes things like apache configuration, requires other applications (like apache, iperf, tcpdump, lldp, etc)
I want to deploy these to multiple boxes, I want them versioned, I want them easy to update, I want to know what version is installed. All of this is handled by a 10 line config file in a .deb.
- Your app isn't licensed in the way you like
- You don't want to provide the source code to your app
- They don't find your app interesting enough
- They don't agree with your app's policies
Static linking is a much better response for actually getting the program running on Linux systems but this still leaves out the "is there a way I can package it once for Linux" problem.
Now if it's a CLI only utility/service type tool it's a different story.
# dpkg-deb --build ./src/ mydeb.deb
Stick your dependencies etc in ./src/DEBIAN/controlOr just distribute a tar with your application.
I used to have to distribute a cross platform java progam. It was horrendous creating the MSI on windows. The linux and osx versions were trivial
Deployed by apt/yum/mdm andslme crappy windows thing. The latter was the problem.
Long story short, it wasn't possible. Because the latest versions of Ubuntu and CentOS at the time used different versions of glibc which were mutually incompatible.
> Static linking of glibc is not supported on Red Hat Enterprise Linux.
https://developers.redhat.com/blog/2016/02/17/upgrading-the-...
:/tmp$ file ffmbc
ffmbc: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
statically linked, for GNU/Linux 2.6.15, BuildID[sha1]=...., stripped
:/tmp$ ldd ffmbc
not a dynamic executable
It runs on the following libc versions: ldd (GNU libc) 2.12 (Centos 6.9)
ldd (Ubuntu EGLIBC 2.19-0ubuntu6.14) 2.19 (ubuntu 14.04)
ldd (Ubuntu GLIBC 2.23-0ubuntu10) 2.23 (Ubuntu 16.04)
I'm fairly sure it ran on distributions as old as ubuntu 8.04* systray icons/application indicators
* newer fontconfig versions support new config options which results in fonts being broken for apps that use older versions
* I think there is some issue around glibc locales handling if you use a different glibc
* systemd/logind/... DBus apis
Those were just some external interfaces that came to my mind. I'm sure there are many more.
This has nothing to do with the OS. A statically linked linux executable will likely run on any kernel from version 1 to version 4. The issue here is dynamic linking, and windows DLL hell has a name for a reason
Which libraries are you including in your description of a statically-linked binary on Linux ?
Finally, Windows DLL hell hasn't been a thing since the early 2000s, and even then, it was primarily only an issue with applications dumping shared libraries into the Windows system directories.
And then sit and scratch your head over why software vendors don't sell a version for your OS.
If so, I wouldn't worry about packaging for every distro. Just make sure that your application isn't difficult to build, and most things (paths, etc.) are configurable. The config path can be handled with a command line switch.
Once you've gotten your application so that it can be built easily, I'd only really worry about packaging it for your distro of choice. If people are interested in your application, it'll get packaged for their distros.
If your application is proprietary, I wouldn't even worry about packaging it yet. Getting most Linux users on non-essential proprietary software will be an uphill battle.
I guess of you're working with gui that's a different story. But for .net websites and background servers, simply use docker on linux and never lool back. And it's much simpler than docker for windows too.
It’s brilliant. It’s one of the reasons I value this site so much.
- AppImage: it packs Linux apps into a tar file so you can unzip it later and run the executable of the app. The main selling point of AppImage is it's distro-independent
- XAR: it packs dynamic languages programs (python, node.js, lua) into a executable file. The executable includes the language runtime, a fuse filesystem to mount the program's source code.
In conclusion, I don't think you ever needs to make an AppImage for XAR executable file.
There are very complex Python applications (e.g., Ultimaker Cura) which are packaged in the AppImage format as self-standing single-file executables, including the Python interpreter, libraries, and other resources.
There's a rapidly growing library of libraries and services packaged with it: https://bldr.habitat.sh
Its build artifacts can be exported to a number of formats including container images and tarballs, maybe a XAR exporter could be built: https://github.com/habitat-sh/habitat/tree/master/components...
I think to most people it would be negligible, but fb operates at a scale where these normally insignificant pieces matter. I would be interested to hear more about the _why_ of a system like this over containerization.
edit: rwmj's comment has a good discussion over the benefits of this over containerization.
One of the key innovations in habitat is that it gives you reliable dependency isolation WITHOUT needing the runtime isolation of containers to achieve it. Containers become optional.
A habitat build artifact can be installed and run natively on nearly _any_ POSIX system, the state of the system doesn't matter and you get the same behavior top-to-bottom. There are a number of "exporters" available to repackage build artifacts into different runtime formats -- Docker container is just one option
The test against native start speed was hot, so the pages required were already in the page cache, so the filesystem shouldn’t matter.
In practice the timings of XAR vs filesystem are close enough to be "in the noise" -- it's when compared to PEX or PARs that the difference is quite large.
black: 0.171 s (vs 0.208 for XAR) jupyter: 0.165 s (vs 0.179 s for XAR)
My test setup used the older loading method because "pip install ." won't install wheels if the wheel package isn't installed in the virtualenv.
I'm curious how it compares to using something like pkg [0].
There are two ways to build a node app using the XAR builder tools. 1. Use the `make_xar` tool which will create a XAR from a directory and takes an optional script to run on execution. 2. Use the XAR builder library to make a XAR builder that is specialized for building node apps.
Python >= 2.7.11 & >= 3.5
you need both?
I don't like a lot about what Facebook does with user data and marketing but their support of open source is better than many companies. Give credit where it is due.
$ ./good_program_i_promise.bug