From f59672679db271f6b24a41e215401ab5001ecd96 Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Tue, 31 Oct 2023 08:38:25 +0100 Subject: Theme updates --- ...021-12-25-running-golang-application-as-pid1.md | 79 +++++++++++----------- 1 file changed, 39 insertions(+), 40 deletions(-) (limited to 'content/posts/2021-12-25-running-golang-application-as-pid1.md') diff --git a/content/posts/2021-12-25-running-golang-application-as-pid1.md b/content/posts/2021-12-25-running-golang-application-as-pid1.md index 251ef4f..e09bbc9 100644 --- a/content/posts/2021-12-25-running-golang-application-as-pid1.md +++ b/content/posts/2021-12-25-running-golang-application-as-pid1.md @@ -13,11 +13,11 @@ I have been reading a lot about very intriguing. When you push away all the marketing speak and look at the idea, it makes a lot of sense. -> A unikernel is a specialized, single address space machine image constructed +> A unikernel is a specialized, single address space machine image constructed > by using library operating systems. ([Wikipedia](https://en.wikipedia.org/wiki/Unikernel)) -I really like the explanation from the article -[Unikernels: Rise of the Virtual Library Operating System](https://queue.acm.org/detail.cfm?id=2566628). +I really like the explanation from the article +[Unikernels: Rise of the Virtual Library Operating System](https://queue.acm.org/detail.cfm?id=2566628). Really worth a read. If we compare a normal operating system to a unikernel side by side, they would @@ -25,14 +25,14 @@ look something like this. ![Virtual machines vs Containers vs Unikernels](/posts/pid1/unikernels.png) -From this image, we can see how the complexity significantly decreases with -the use of Unikernels. This comes with a price, of course. Unikernels are hard +From this image, we can see how the complexity significantly decreases with +the use of Unikernels. This comes with a price, of course. Unikernels are hard to get running and require a lot of work since you don't have an actual proper kernel running in the background providing network access and drivers etc. -So as a half step to make the stack simpler, I started looking into using -Linux kernel as a base and going from there. I came across this -[Youtube video talking about Building the Simplest Possible Linux System](https://www.youtube.com/watch?v=Sk9TatW9ino) +So as a half step to make the stack simpler, I started looking into using +Linux kernel as a base and going from there. I came across this +[Youtube video talking about Building the Simplest Possible Linux System](https://www.youtube.com/watch?v=Sk9TatW9ino) by [Rob Landley](https://landley.net) and apart from statically compiling the application to be run as PID1 there was really no other obstacles. @@ -41,12 +41,12 @@ application to be run as PID1 there was really no other obstacles. PID 1 is the first process that Linux kernel starts after the boot process. It also has a couple of unique properties that are unique to it. -- When the process with PID 1 dies for any reason, all other processes are +- When the process with PID 1 dies for any reason, all other processes are killed with KILL signal. -- When any process having children dies for any reason, its children are +- When any process having children dies for any reason, its children are re-parented to process with PID 1. - Many signals which have default action of Term do not have one for PID 1. -- When the process with PID 1 dies for any reason, kernel panics, which +- When the process with PID 1 dies for any reason, kernel panics, which result in system crash. PID 1 is considered as an Init application which takes care of running other @@ -73,19 +73,19 @@ PPid: 0 ``` As we can see on my machine the process with id of 1 is [systemd](https://systemd.io/) -which is a software suite that provides an array of system components for Linux -operating systems. If you look closely you can also see that the `PPid` -(process id of the parent process) is `0` which additionally confirms that +which is a software suite that provides an array of system components for Linux +operating systems. If you look closely you can also see that the `PPid` +(process id of the parent process) is `0` which additionally confirms that this process doesn't have a parent. ## So why even run application as PID 1 instead of just using a container? -Containers are wonderful, but they come with a lot of baggage. And because they -are in their nature layered, the images require quite a lot of space and also a +Containers are wonderful, but they come with a lot of baggage. And because they +are in their nature layered, the images require quite a lot of space and also a lot of additional software to handle them. They are not as lightweight as they seem, and many popular images require 500 MB plus disk space. -The idea of running this as PID 1 would result in a significantly smaller footprint, +The idea of running this as PID 1 would result in a significantly smaller footprint, as we will see later in the post. > You could run a simple init system inside Docker container described more @@ -95,7 +95,7 @@ as we will see later in the post. 1. Compile Linux kernel with the default definitions. 2. Prepare a Hello World application in Golang that is statically compiled. -3. Run it with [QEMU](https://www.qemu.org/) and providing Golang application +3. Run it with [QEMU](https://www.qemu.org/) and providing Golang application as init application / PID 1. For the sake of simplicity we will not be cross-compiling any of it and just @@ -122,16 +122,16 @@ $ cd .. At this point we have kernel image that is located in `arch/x86_64/boot/bzImage`. We will use this in QEMU later. -To make our lives a bit easier lets move the kernel image to another place. +To make our lives a bit easier lets move the kernel image to another place. Lets create a folder `bin/` in the root of our project with `mkdir -p bin`. -At this point we can copy `bzImage` to `bin/` folder with +At this point we can copy `bzImage` to `bin/` folder with `cp linux-5.15.7/arch/x86_64/boot/bzImage bin/bzImage`. The folder structure of this experiment should look like this. -``` +```txt pid1/ bin/ bzImage @@ -141,7 +141,7 @@ pid1/ ## Preparing PID 1 application in Golang -This step is relatively easy. The only thing we must have in mind that we will +This step is relatively easy. The only thing we must have in mind that we will need to compile the binary as a static one. Let's create `init.go` file in the root of the project. @@ -162,8 +162,8 @@ func main() { } ``` -If you notice, we have a forever loop in the main, with a simple sleep of 1 -second to not overwhelm the CPU. This is because PID 1 should never complete +If you notice, we have a forever loop in the main, with a simple sleep of 1 +second to not overwhelm the CPU. This is because PID 1 should never complete and/or exit. That would result in a kernel panic. Which is BAD! There are two ways of compiling Golang application. Statically and dynamically. @@ -184,9 +184,9 @@ $ ldd init not a dynamic executable ``` -At this point, we need to create [initramfs](https://www.linuxfromscratch.org/blfs/view/svn/postlfs/initramfs.html) -(abbreviated from "initial RAM file system", is the successor of initrd. It -is a cpio archive of the initial file system that gets loaded into memory +At this point, we need to create [initramfs](https://www.linuxfromscratch.org/blfs/view/svn/postlfs/initramfs.html) +(abbreviated from "initial RAM file system", is the successor of initrd. It +is a cpio archive of the initial file system that gets loaded into memory during the Linux startup process). ```sh @@ -196,7 +196,7 @@ $ mv initramfs bin/initramfs The projects at this stage should look like this. -``` +```txt pid1/ bin/ bzImage @@ -209,7 +209,7 @@ pid1/ ## Running all of it with QEMU [QEMU](https://www.qemu.org/) is a free and open-source hypervisor. It emulates -the machine's processor through dynamic binary translation and provides a set +the machine's processor through dynamic binary translation and provides a set of different hardware and device models for the machine, enabling it to run a variety of guest operating systems. @@ -256,11 +256,11 @@ The whole [log file here](/posts/pid1/qemu.log). ## Size comparison -The cool thing about this approach is that the Linux kernel and the application +The cool thing about this approach is that the Linux kernel and the application together only take around 12 MB, which is impressive as hell. And we need to -also know that the size of bzImage (Linux kernel) could be greatly decreased +also know that the size of bzImage (Linux kernel) could be greatly decreased by going into `make menuconfig` and removing a ton of features from the kernel, -making the size even smaller. I managed to get kernel size down to 2 MB and +making the size even smaller. I managed to get kernel size down to 2 MB and still working properly. ```sh @@ -325,24 +325,23 @@ genisoimage -R \ iso ``` -This will produce `GoAsPID1.iso` which you can use with [Virtualbox](https://www.virtualbox.org/) +This will produce `GoAsPID1.iso` which you can use with [Virtualbox](https://www.virtualbox.org/) or [Gnome Boxes](https://apps.gnome.org/app/org.gnome.Boxes/). ## Is running applications as PID 1 even worth it? -Well, the answer to this is not as simple as one would think. Sometimes it is +Well, the answer to this is not as simple as one would think. Sometimes it is and sometimes it's not. For embedded systems and very specialized applications it is worth for sure. But in normal uses, I don't think so. It was an interesting -exercise in compiling kernels and looking at the guts of the Linux kernel, -but sticking to containers for most of the things is a better option in my +exercise in compiling kernels and looking at the guts of the Linux kernel, +but sticking to containers for most of the things is a better option in my opinion. An interesting experiment would be creating an image that supports networking -and could be deployed to AWS as an EC2 instance and observing how it fares. -But in that case, we would need to write some sort of supervisor that would -run on a separate EC2 that would check if other EC2 instances are running +and could be deployed to AWS as an EC2 instance and observing how it fares. +But in that case, we would need to write some sort of supervisor that would +run on a separate EC2 that would check if other EC2 instances are running properly. Remember that if your application fails, kernel panics and the whole machine is inoperable in this case. - -- cgit v1.2.3