Open andrewstucki opened 4 years ago
Pinging @elastic/siem (Team:SIEM)
For some more statistics, here's all the builds I tried (non-OSS was done just to get a feel for the size reduction)
Before:
[root@localhost]# ls -la metricbeat
-rwxr-xr-x. 1 root root 124485568 Jun 14 18:23 metricbeat
After:
/src/x-pack/metricbeat # CC=/usr/bin/x86_64-alpine-linux-musl-gcc go build -mod=mod -ldflags '-extldflags "-static" -s'
/src/x-pack/metricbeat # ls -la metricbeat
-rwxr-xr-x 1 root root 101149856 Jul 14 21:50 metricbeat
/src/x-pack/metricbeat # upx -9 metricbeat
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2020
UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020
File size Ratio Format Name
-------------------- ------ ----------- -----------
101149856 -> 27441836 27.13% linux/amd64 metricbeat
Packed 1 file.
static musl + stripped: 81% original size static musl + stripped + packed: 22% original size
Before:
[root@localhost]# ls -la packetbeat
-rwxr-xr-x. 1 root root 77414552 Jun 14 17:49 packetbeat
After:
/src/x-pack/packetbeat # CC=/usr/bin/x86_64-alpine-linux-musl-gcc go build -mod=mod -ldflags '-extldflags "-static" -s'
/src/x-pack/packetbeat # ls -la packetbeat
-rwxr-xr-x 1 root root 65854872 Jul 14 21:29 packetbeat
/src/x-pack/packetbeat # upx -9 packetbeat
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2020
UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020
File size Ratio Format Name
-------------------- ------ ----------- -----------
65854872 -> 19294968 29.30% linux/amd64 packetbeat
Packed 1 file.
static musl + stripped: 85% original size static musl + stripped + packed: 25% original size
Before:
[root@localhost]# ls -la filebeat
-rwxr-xr-x. 1 root root 89226256 Jun 14 18:17 filebeat
After:
/src/x-pack/filebeat # CC=/usr/bin/x86_64-alpine-linux-musl-gcc go build -mod=mod -ldflags '-extldflags "-static" -s'
/src/x-pack/filebeat # ls -la filebeat
-rwxr-xr-x 1 root root 76930584 Jul 14 21:31 filebeat
/src/x-pack/filebeat # upx -9 filebeat
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2020
UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020
File size Ratio Format Name
-------------------- ------ ----------- -----------
76930584 -> 22323776 29.02% linux/amd64 filebeat
Packed 1 file.
static musl + stripped: 86% original size static musl + stripped + packed: 25% original size
Before:
[root@localhost]# ls -la auditbeat
-rwxr-xr-x. 1 root root 78971184 Jun 14 18:13 auditbeat
After:
/src/x-pack/auditbeat # CC=/usr/bin/x86_64-alpine-linux-musl-gcc go build -mod=mod -ldflags '-extldflags "-static" -s'
/src/x-pack/auditbeat # ls -la auditbeat
-rwxr-xr-x 1 root root 67057816 Jul 14 20:57 auditbeat
/src/x-pack/auditbeat # upx -9 auditbeat
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2020
UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020
File size Ratio Format Name
-------------------- ------ ----------- -----------
67057816 -> 19599108 29.23% linux/amd64 auditbeat
Packed 1 file.
static musl + stripped: 84% original size static musl + stripped + packed: 25% original size
Before:
[root@localhost]# ls -la heartbeat
-rwxr-xr-x. 1 root root 74062704 Jun 14 17:32 heartbeat
After:
/src/x-pack/heartbeat # CC=/usr/bin/x86_64-alpine-linux-musl-gcc go build -mod=mod -ldflags '-extldflags "-static" -s'
/src/x-pack/heartbeat # ls -la heartbeat
-rwxr-xr-x 1 root root 63126904 Jul 14 21:36 heartbeat
/src/x-pack/heartbeat # upx -9 heartbeat
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2020
UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020
File size Ratio Format Name
-------------------- ------ ----------- -----------
63126904 -> 18286088 28.97% linux/amd64 heartbeat
Packed 1 file.
static musl + stripped: 85% original size static musl + stripped + packed: 25% original size
Before:
[root@localhost]# ls -la functionbeat
-rwxr-xr-x. 1 root root 74990832 Jun 14 17:53 functionbeat
After:
/src/x-pack/functionbeat # CC=/usr/bin/x86_64-alpine-linux-musl-gcc go build -mod=mod -ldflags '-extldflags "-static" -s'
/src/x-pack/functionbeat # ls -la functionbeat
-rwxr-xr-x 1 root root 64279352 Jul 14 21:39 functionbeat
/src/x-pack/functionbeat # upx -9 functionbeat
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2020
UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020
File size Ratio Format Name
-------------------- ------ ----------- -----------
64279352 -> 18753196 29.17% linux/amd64 functionbeat
Packed 1 file.
static musl + stripped: 86% original size static musl + stripped + packed: 25% original size
Can't currently build on alpine due to lack of system-available systemd headers. I'm sure we could either port the headers or build a musl-based toolchain on another distro to get this to work.
Just FYI I did a bit more experimentation and was able to cross-compile most architectures out of the box with compilers I found at https://musl.cc/ -- the exceptions: arm*
due to the binutils in the toolchains not coming with the gold
linker, which, for some reason the go compiler has an explicit check for on arm (so we'd need to modify/build a toolchain with gold
if we'd want that to work). And ppc64
, which is a known thing to not support CGO due to the go compiler's lack of external linking on ppc64
.
The only other snafu I ran into was seeing that the auditbeat
system/package
module uses dlopen
to dynamically link in different version of librpm
for 1. licensing purposes (it's LGPL), and 2. portability. dlopen
on musl
is stubbed, so it wouldn't work with a static build even though it compiles fine. I wonder if a native go implementation might work? Something like https://github.com/cavaliercoder/go-rpm?
Here are final cross-compiled/packed metricbeats (notice no upx support for 64-bit mips or s390x architectures):
/src/x-pack/metricbeat/builds # ls -la
total 422492
drwxr-xr-x 10 root root 320 Jul 15 12:43 .
drwxr-xr-x 25 root root 800 Jul 15 12:43 ..
-rwxr-xr-x 1 root root 25938880 Jul 15 12:42 metricbeat.386
-rwxr-xr-x 1 root root 27449944 Jul 15 12:42 metricbeat.amd64
-rwxr-xr-x 1 root root 22985804 Jul 15 12:42 metricbeat.mips
-rwxr-xr-x 1 root root 103886880 Jul 15 12:42 metricbeat.mips64
-rwxr-xr-x 1 root root 103886784 Jul 15 12:43 metricbeat.mips64le
-rwxr-xr-x 1 root root 22935320 Jul 15 12:43 metricbeat.mipsle
-rwxr-xr-x 1 root root 24749764 Jul 15 12:43 metricbeat.ppc64le
-rwxr-xr-x 1 root root 100784064 Jul 15 12:43 metricbeat.s390x
For any other architecture where we can't fully statically link (like OSX) we can still likely run it through a packer and save space on disk if we want to, but there's less compelling of a reason to because if we just gzip our binaries for distribution we get about the same savings for the actual distribution as compressing sections with something like upx.
So, final thoughts and I'll let (hopefully) others chime in:
musl
for portability, we can likely do this with other *nix distros if we have supported builds for them too with their native libc since most other libc are not GPL'd like glibcupx
to pack them--the concern for this isn't as big since we only get a small win on distributing the binaries since we make tarballs/rpms/debs of most things for distribution anyway and all of these are already gzipped. It would, however, give us a cost savings on disk.Pinging @urso / @kvch / @ph in case you want to chime in since I've seen you active in some of the build/toolchain work for beats recently.
Great initiative @andrewstucki, would love to see our binaries losing some weight.
Is this intended for Linux only? I'm curious about the usage of upx
for Windows binaries, whether it can cause some trouble with binary analyzers flagging us as malware or is that a thing of the past?
Also, do you think the usage of upx -9
can cause a significant increase in startup times? IIRC there was some effort in reducing Functionbeat's startup times, but I guess this is probably a drop in the ocean.
@adriansr , so yeah those are valid points. Bringing this up in the Linux context primarily because I artificially joined the two issues of static Linux builds and code packing together--namely because both change the way we build out the binaries and have size implications. 😬
That said, with regard to packing. I ran a 64 bit dynamically linked mac build at the default upx packing level and saw an ~110MB drop in the file size of a metricbeat build. I'm fairly certain we'd see comparable gains for Windows binaries. The way upx generally works is that it goes through and compresses the sections of your binary and replaces your binary entrypoint with a little unpacking trampoline that decompresses the sections and then jumps to the real entrypoint--so as you might imagine, there is a bit of a startup cost, I would imagine part of this is correlated with the size of the binary (more code == more to decompress).
What that is in terms of Functionbeat start-up times I'm not entirely sure, but I'm sure we could attempt the compression at multiple levels and test out startup time with something simple like a time ./functionbeat --help > /dev/null
.
With regard to Malware--I'm not entirely sure I can say. I know that things like section entropy are taken into consideration when doing malware analysis since sometimes malware authors do things like run their code through a packer and then modify the packer signatures to make it harder to reverse, but I'm pretty sure that with most AV this is only one of many considerations. Just for kicks I dropped the Linux and Mac packed builds into VirusTotal and nothing flagged them, but no guarantees on Windows until we tried it.
So yeah, I'm not entirely convinced on packing due to the above reasons and because of the fact that we already compress the files we distribute as tarballs, so we're really just saving bytes on disk (thought that is nice). That said, I think the static compilation on Linux would be a win since we'd no longer have to worry about building with old glibc for backwards compatibility across distros and/or lack of support on Linux flavors shipping with alternative libc implementations.
The only other snafu I ran into was seeing that the auditbeat system/package module uses dlopen to dynamically link in different version of librpm for 1. licensing purposes (it's LGPL), and 2. portability. dlopen on musl is stubbed, so it wouldn't work with a static build even though it compiles fine. I wonder if a native go implementation might work? Something like cavaliercoder/go-rpm?
dlopen MUST function. It is not just librpm, but other C-libs as well. For example libs for Oracle Database we can't just link statically. There is always the chance that there is no go-native library. This is why we enable CGO in the past (originally Beats have been build with CGO disabled).
When statically linking MUSL, it becomes a hard dependency. Bugs and security issues in the MUSL version used will be Beats bugs as well. With dynamic linking we don't care, as libc security issues should be handled by the user/distro.
on stripping: The go compiler includes all debug symbols twice. For once in an internal section and a second timw as DWARF. delve and the go built in profile do not even use the DWARF. +1 on always stripping our builds.
on upx: We've discussed this a few times in the past and decided not to use it. Even on Linux I wonder how upx interacts with apparmor or even more aggressive system hardening. For saving network bandwith tar.bz2 should be good enough. Users can always opt to compress binaries locally via upx, but I don't think its worth it to package them out of the box.
glibc devs spend a huge amount of effort in optimizing performance. Do we have an idea if/how MUSL affects resource usage for Beats?
@nimarezainia is it something we should still think about?
Pinging @elastic/security-scalability (Team:Security-Scalability)
Describe the enhancement:
Shrink the size of our release binaries and make them more portable.
Describe a specific use case for the enhancement or feature:
So right now our beats are objectively large. our Elastic licensed stripped
auditbeat
build clocks in at roughly 79MB. Currently we build our beats using gcc linking against GPLglibc
. Large go projects often produce smaller statically stripped binaries when using static linking, likely in part due to additional compiler optimizations that can occur due to the static link as well as getting rid of the huge dynamic symbol table left when linking dynamically.The problem with static linking is that we can't statically link against
glibc
because then our binaries would need to be GPL-compatible. So I did a quick test with amusl
libc-based build in an alpine container and here are the basic results:As an added benefit, we would now get portability across operating systems with incompatible system libc.
As a secondary test I ran the static binary through UPX just to see how well packing would affect the binary:
So, if we went with something like that we'd basically have roughly 1/4 of the binary size while being able to support more systems out of the box.
WDYT?