aboutsummaryrefslogtreecommitdiff
path: root/content/posts/2021-12-25-running-golang-application-as-pid1.md
diff options
context:
space:
mode:
Diffstat (limited to 'content/posts/2021-12-25-running-golang-application-as-pid1.md')
-rw-r--r--content/posts/2021-12-25-running-golang-application-as-pid1.md122
1 files changed, 91 insertions, 31 deletions
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 3d1f266..e3b7fa2 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
@@ -1,43 +1,62 @@
1--- 1---
2title: Running Golang application as PID 1 with Linux kernel 2title: Running Golang application as PID 1 with Linux kernel
3url: running-golang-application-as-pid1.html 3url: running-golang-application-as-pid1.html
4date: 2021-12-25 4date: 2021-12-25T12:00:00+02:00
5draft: false 5draft: false
6--- 6---
7 7
8## Unikernels, kernels, and alike 8## Unikernels, kernels, and alike
9 9
10I have been reading a lot about [unikernernels](https://en.wikipedia.org/wiki/Unikernel) lately and found them very intriguing. When you push away all the marketing speak and look at the idea, it makes a lot of sense. 10I have been reading a lot about [unikernernels](https://en.wikipedia.org/wiki/Unikernel)
11lately and found them very intriguing. When you push away all the marketing
12speak and look at the idea, it makes a lot of sense.
11 13
12> A unikernel is a specialized, single address space machine image constructed by using library operating systems. ([Wikipedia](https://en.wikipedia.org/wiki/Unikernel)) 14> A unikernel is a specialized, single address space machine image constructed
15> by using library operating systems. ([Wikipedia](https://en.wikipedia.org/wiki/Unikernel))
13 16
14I 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. 17I really like the explanation from the article
18[Unikernels: Rise of the Virtual Library Operating System](https://queue.acm.org/detail.cfm?id=2566628).
19Really worth a read.
15 20
16If we compare a normal operating system to a unikernel side by side, they would look something like this. 21If we compare a normal operating system to a unikernel side by side, they would
22look something like this.
17 23
18![Virtual machines vs Containers vs Unikernels](/assets/pid1/unikernels.png) 24![Virtual machines vs Containers vs Unikernels](/assets/pid1/unikernels.png)
19 25
20From 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. 26From this image, we can see how the complexity significantly decreases with
27the use of Unikernels. This comes with a price, of course. Unikernels are hard
28to get running and require a lot of work since you don't have an actual proper
29kernel running in the background providing network access and drivers etc.
21 30
22So 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. 31So as a half step to make the stack simpler, I started looking into using
32Linux kernel as a base and going from there. I came across this
33[Youtube video talking about Building the Simplest Possible Linux System](https://www.youtube.com/watch?v=Sk9TatW9ino)
34by [Rob Landley](https://landley.net) and apart from statically compiling the
35application to be run as PID1 there was really no other obstacles.
23 36
24## What is PID 1? 37## What is PID 1?
25 38
26PID 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. 39PID 1 is the first process that Linux kernel starts after the boot process.
40It also has a couple of unique properties that are unique to it.
27 41
28- When the process with PID 1 dies for any reason, all other processes are killed with KILL signal. 42- When the process with PID 1 dies for any reason, all other processes are
29- When any process having children dies for any reason, its children are re-parented to process with PID 1. 43 killed with KILL signal.
44- When any process having children dies for any reason, its children are
45 re-parented to process with PID 1.
30- Many signals which have default action of Term do not have one for PID 1. 46- Many signals which have default action of Term do not have one for PID 1.
31- When the process with PID 1 dies for any reason, kernel panics, which result in system crash. 47- When the process with PID 1 dies for any reason, kernel panics, which
48 result in system crash.
32 49
33PID 1 is considered as an Init application which takes care of running other and handling services like: 50PID 1 is considered as an Init application which takes care of running other
51and handling services like:
34 52
35- sshd, 53- sshd,
36- nginx, 54- nginx,
37- pulseaudio, 55- pulseaudio,
38- etc. 56- etc.
39 57
40If you are on a Linux machine, you can check what your process is with PID 1 by running the following. 58If you are on a Linux machine, you can check what your process is with PID 1
59by running the following.
41 60
42```sh 61```sh
43$ cat /proc/1/status 62$ cat /proc/1/status
@@ -51,23 +70,34 @@ PPid: 0
51... 70...
52``` 71```
53 72
54As 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 this process doesn't have a parent. 73As we can see on my machine the process with id of 1 is [systemd](https://systemd.io/)
74which is a software suite that provides an array of system components for Linux
75operating systems. If you look closely you can also see that the `PPid`
76(process id of the parent process) is `0` which additionally confirms that
77this process doesn't have a parent.
55 78
56## So why even run application as PID 1 instead of just using a container? 79## So why even run application as PID 1 instead of just using a container?
57 80
58Containers 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. 81Containers are wonderful, but they come with a lot of baggage. And because they
82are in their nature layered, the images require quite a lot of space and also a
83lot of additional software to handle them. They are not as lightweight as they
84seem, and many popular images require 500 MB plus disk space.
59 85
60The idea of running this as PID 1 would result in a significantly smaller footprint, as we will see later in the post. 86The idea of running this as PID 1 would result in a significantly smaller footprint,
87as we will see later in the post.
61 88
62> You could run a simple init system inside Docker container described more in this article [Docker and the PID 1 zombie reaping problem](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/). 89> You could run a simple init system inside Docker container described more
90> in this article [Docker and the PID 1 zombie reaping problem](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/).
63 91
64## The master plan 92## The master plan
65 93
661. Compile Linux kernel with the default definitions. 941. Compile Linux kernel with the default definitions.
672. Prepare a Hello World application in Golang that is statically compiled. 952. Prepare a Hello World application in Golang that is statically compiled.
683. Run it with [QEMU](https://www.qemu.org/) and providing Golang application as init application / PID 1. 963. Run it with [QEMU](https://www.qemu.org/) and providing Golang application
97 as init application / PID 1.
69 98
70For the sake of simplicity we will not be cross-compiling any of it and just use the 64bit version. 99For the sake of simplicity we will not be cross-compiling any of it and just
100use the 64bit version.
71 101
72## Compiling Linux kernel 102## Compiling Linux kernel
73 103
@@ -87,12 +117,15 @@ $ time make -j `nproc`
87$ cd .. 117$ cd ..
88``` 118```
89 119
90At this point we have kernel image that is located in `arch/x86_64/boot/bzImage`. We will use this in QEMU later. 120At this point we have kernel image that is located in `arch/x86_64/boot/bzImage`.
121We will use this in QEMU later.
91 122
92To 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`. 123To make our lives a bit easier lets move the kernel image to another place.
124Lets create a folder `bin/` in the root of our project with `mkdir -p bin`.
93 125
94 126
95At this point we can copy `bzImage` to `bin/` folder with `cp linux-5.15.7/arch/x86_64/boot/bzImage bin/bzImage`. 127At this point we can copy `bzImage` to `bin/` folder with
128`cp linux-5.15.7/arch/x86_64/boot/bzImage bin/bzImage`.
96 129
97The folder structure of this experiment should look like this. 130The folder structure of this experiment should look like this.
98 131
@@ -106,7 +139,8 @@ pid1/
106 139
107## Preparing PID 1 application in Golang 140## Preparing PID 1 application in Golang
108 141
109This step is relatively easy. The only thing we must have in mind that we will need to compile the binary as a static one. 142This step is relatively easy. The only thing we must have in mind that we will
143need to compile the binary as a static one.
110 144
111Let's create `init.go` file in the root of the project. 145Let's create `init.go` file in the root of the project.
112 146
@@ -126,7 +160,9 @@ func main() {
126} 160}
127``` 161```
128 162
129If 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! 163If you notice, we have a forever loop in the main, with a simple sleep of 1
164second to not overwhelm the CPU. This is because PID 1 should never complete
165and/or exit. That would result in a kernel panic. Which is BAD!
130 166
131There are two ways of compiling Golang application. Statically and dynamically. 167There are two ways of compiling Golang application. Statically and dynamically.
132 168
@@ -146,7 +182,10 @@ $ ldd init
146not a dynamic executable 182not a dynamic executable
147``` 183```
148 184
149At 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). 185At this point, we need to create [initramfs](https://www.linuxfromscratch.org/blfs/view/svn/postlfs/initramfs.html)
186(abbreviated from "initial RAM file system", is the successor of initrd. It
187is a cpio archive of the initial file system that gets loaded into memory
188during the Linux startup process).
150 189
151```sh 190```sh
152$ echo init | cpio -o --format=newc > initramfs 191$ echo init | cpio -o --format=newc > initramfs
@@ -167,7 +206,10 @@ pid1/
167 206
168## Running all of it with QEMU 207## Running all of it with QEMU
169 208
170[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 of different hardware and device models for the machine, enabling it to run a variety of guest operating systems. 209[QEMU](https://www.qemu.org/) is a free and open-source hypervisor. It emulates
210the machine's processor through dynamic binary translation and provides a set
211of different hardware and device models for the machine, enabling it to run a
212variety of guest operating systems.
171 213
172```sh 214```sh
173$ qemu-system-x86_64 -serial stdio -kernel bin/bzImage -initrd bin/initramfs -append "console=ttyS0" -m 128 215$ qemu-system-x86_64 -serial stdio -kernel bin/bzImage -initrd bin/initramfs -append "console=ttyS0" -m 128
@@ -212,7 +254,12 @@ The whole [log file here](/assets/pid1/qemu.log).
212 254
213## Size comparison 255## Size comparison
214 256
215The 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 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 still working properly. 257The cool thing about this approach is that the Linux kernel and the application
258together only take around 12 MB, which is impressive as hell. And we need to
259also know that the size of bzImage (Linux kernel) could be greatly decreased
260by going into `make menuconfig` and removing a ton of features from the kernel,
261making the size even smaller. I managed to get kernel size down to 2 MB and
262still working properly.
216 263
217```sh 264```sh
218total 12M 265total 12M
@@ -224,7 +271,8 @@ total 12M
224 271
225First we need to create proper folder structure with `mkdir -p iso/boot/grub`. 272First we need to create proper folder structure with `mkdir -p iso/boot/grub`.
226 273
227Then we need to download the [grub binary](https://github.com/littleosbook/littleosbook/raw/master/files/stage2_eltorito). You can read more about this program on https://github.com/littleosbook/littleosbook. 274Then we need to download the [grub binary](https://github.com/littleosbook/littleosbook/raw/master/files/stage2_eltorito).
275You can read more about this program on https://github.com/littleosbook/littleosbook.
228 276
229```sh 277```sh
230$ wget -O iso/boot/grub/stage2_eltorito https://github.com/littleosbook/littleosbook/raw/master/files/stage2_eltorito 278$ wget -O iso/boot/grub/stage2_eltorito https://github.com/littleosbook/littleosbook/raw/master/files/stage2_eltorito
@@ -275,12 +323,24 @@ genisoimage -R \
275 iso 323 iso
276``` 324```
277 325
278This 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/). 326This will produce `GoAsPID1.iso` which you can use with [Virtualbox](https://www.virtualbox.org/)
327or [Gnome Boxes](https://apps.gnome.org/app/org.gnome.Boxes/).
279 328
280<video src="/assets/pid1/boxes.mp4" controls></video> 329<video src="/assets/pid1/boxes.mp4" controls></video>
281 330
282## Is running applications as PID 1 even worth it? 331## Is running applications as PID 1 even worth it?
283 332
284Well, 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 opinion. 333Well, the answer to this is not as simple as one would think. Sometimes it is
334and sometimes it's not. For embedded systems and very specialized applications
335it is worth for sure. But in normal uses, I don't think so. It was an interesting
336exercise in compiling kernels and looking at the guts of the Linux kernel,
337but sticking to containers for most of the things is a better option in my
338opinion.
339
340An interesting experiment would be creating an image that supports networking
341and could be deployed to AWS as an EC2 instance and observing how it fares.
342But in that case, we would need to write some sort of supervisor that would
343run on a separate EC2 that would check if other EC2 instances are running
344properly. Remember that if your application fails, kernel panics and the
345whole machine is inoperable in this case.
285 346
286An 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 properly. Remember that if your application fails, kernel panics and the whole machine is inoperable in this case.