aboutsummaryrefslogtreecommitdiff
path: root/content/posts/2021-12-25-running-golang-application-as-pid1.md
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2023-10-31 08:38:25 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2023-10-31 08:38:25 +0100
commitf59672679db271f6b24a41e215401ab5001ecd96 (patch)
tree367e04e449d8b02266b05d37413cfd2ba2a80b3a /content/posts/2021-12-25-running-golang-application-as-pid1.md
parent22f5ac8fcec4dbfcc1f5033f18bc4eeca52a747d (diff)
downloadmitjafelicijan.com-f59672679db271f6b24a41e215401ab5001ecd96.tar.gz
Theme updates
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.md79
1 files changed, 39 insertions, 40 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 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
13very intriguing. When you push away all the marketing speak and look at the 13very intriguing. When you push away all the marketing speak and look at the
14idea, it makes a lot of sense. 14idea, it makes a lot of sense.
15 15
16> A unikernel is a specialized, single address space machine image constructed 16> A unikernel is a specialized, single address space machine image constructed
17> by using library operating systems. ([Wikipedia](https://en.wikipedia.org/wiki/Unikernel)) 17> by using library operating systems. ([Wikipedia](https://en.wikipedia.org/wiki/Unikernel))
18 18
19I really like the explanation from the article 19I really like the explanation from the article
20[Unikernels: Rise of the Virtual Library Operating System](https://queue.acm.org/detail.cfm?id=2566628). 20[Unikernels: Rise of the Virtual Library Operating System](https://queue.acm.org/detail.cfm?id=2566628).
21Really worth a read. 21Really worth a read.
22 22
23If we compare a normal operating system to a unikernel side by side, they would 23If we compare a normal operating system to a unikernel side by side, they would
@@ -25,14 +25,14 @@ look something like this.
25 25
26![Virtual machines vs Containers vs Unikernels](/posts/pid1/unikernels.png) 26![Virtual machines vs Containers vs Unikernels](/posts/pid1/unikernels.png)
27 27
28From this image, we can see how the complexity significantly decreases with 28From this image, we can see how the complexity significantly decreases with
29the use of Unikernels. This comes with a price, of course. Unikernels are hard 29the use of Unikernels. This comes with a price, of course. Unikernels are hard
30to get running and require a lot of work since you don't have an actual proper 30to get running and require a lot of work since you don't have an actual proper
31kernel running in the background providing network access and drivers etc. 31kernel running in the background providing network access and drivers etc.
32 32
33So as a half step to make the stack simpler, I started looking into using 33So as a half step to make the stack simpler, I started looking into using
34Linux kernel as a base and going from there. I came across this 34Linux kernel as a base and going from there. I came across this
35[Youtube video talking about Building the Simplest Possible Linux System](https://www.youtube.com/watch?v=Sk9TatW9ino) 35[Youtube video talking about Building the Simplest Possible Linux System](https://www.youtube.com/watch?v=Sk9TatW9ino)
36by [Rob Landley](https://landley.net) and apart from statically compiling the 36by [Rob Landley](https://landley.net) and apart from statically compiling the
37application to be run as PID1 there was really no other obstacles. 37application to be run as PID1 there was really no other obstacles.
38 38
@@ -41,12 +41,12 @@ application to be run as PID1 there was really no other obstacles.
41PID 1 is the first process that Linux kernel starts after the boot process. 41PID 1 is the first process that Linux kernel starts after the boot process.
42It also has a couple of unique properties that are unique to it. 42It also has a couple of unique properties that are unique to it.
43 43
44- When the process with PID 1 dies for any reason, all other processes are 44- When the process with PID 1 dies for any reason, all other processes are
45 killed with KILL signal. 45 killed with KILL signal.
46- When any process having children dies for any reason, its children are 46- When any process having children dies for any reason, its children are
47 re-parented to process with PID 1. 47 re-parented to process with PID 1.
48- Many signals which have default action of Term do not have one for PID 1. 48- Many signals which have default action of Term do not have one for PID 1.
49- When the process with PID 1 dies for any reason, kernel panics, which 49- When the process with PID 1 dies for any reason, kernel panics, which
50 result in system crash. 50 result in system crash.
51 51
52PID 1 is considered as an Init application which takes care of running other 52PID 1 is considered as an Init application which takes care of running other
@@ -73,19 +73,19 @@ PPid: 0
73``` 73```
74 74
75As we can see on my machine the process with id of 1 is [systemd](https://systemd.io/) 75As we can see on my machine the process with id of 1 is [systemd](https://systemd.io/)
76which is a software suite that provides an array of system components for Linux 76which is a software suite that provides an array of system components for Linux
77operating systems. If you look closely you can also see that the `PPid` 77operating systems. If you look closely you can also see that the `PPid`
78(process id of the parent process) is `0` which additionally confirms that 78(process id of the parent process) is `0` which additionally confirms that
79this process doesn't have a parent. 79this process doesn't have a parent.
80 80
81## So why even run application as PID 1 instead of just using a container? 81## So why even run application as PID 1 instead of just using a container?
82 82
83Containers are wonderful, but they come with a lot of baggage. And because they 83Containers are wonderful, but they come with a lot of baggage. And because they
84are in their nature layered, the images require quite a lot of space and also a 84are in their nature layered, the images require quite a lot of space and also a
85lot of additional software to handle them. They are not as lightweight as they 85lot of additional software to handle them. They are not as lightweight as they
86seem, and many popular images require 500 MB plus disk space. 86seem, and many popular images require 500 MB plus disk space.
87 87
88The idea of running this as PID 1 would result in a significantly smaller footprint, 88The idea of running this as PID 1 would result in a significantly smaller footprint,
89as we will see later in the post. 89as we will see later in the post.
90 90
91> You could run a simple init system inside Docker container described more 91> You could run a simple init system inside Docker container described more
@@ -95,7 +95,7 @@ as we will see later in the post.
95 95
961. Compile Linux kernel with the default definitions. 961. Compile Linux kernel with the default definitions.
972. Prepare a Hello World application in Golang that is statically compiled. 972. Prepare a Hello World application in Golang that is statically compiled.
983. Run it with [QEMU](https://www.qemu.org/) and providing Golang application 983. Run it with [QEMU](https://www.qemu.org/) and providing Golang application
99 as init application / PID 1. 99 as init application / PID 1.
100 100
101For the sake of simplicity we will not be cross-compiling any of it and just 101For the sake of simplicity we will not be cross-compiling any of it and just
@@ -122,16 +122,16 @@ $ cd ..
122At this point we have kernel image that is located in `arch/x86_64/boot/bzImage`. 122At this point we have kernel image that is located in `arch/x86_64/boot/bzImage`.
123We will use this in QEMU later. 123We will use this in QEMU later.
124 124
125To make our lives a bit easier lets move the kernel image to another place. 125To make our lives a bit easier lets move the kernel image to another place.
126Lets create a folder `bin/` in the root of our project with `mkdir -p bin`. 126Lets create a folder `bin/` in the root of our project with `mkdir -p bin`.
127 127
128 128
129At this point we can copy `bzImage` to `bin/` folder with 129At this point we can copy `bzImage` to `bin/` folder with
130`cp linux-5.15.7/arch/x86_64/boot/bzImage bin/bzImage`. 130`cp linux-5.15.7/arch/x86_64/boot/bzImage bin/bzImage`.
131 131
132The folder structure of this experiment should look like this. 132The folder structure of this experiment should look like this.
133 133
134``` 134```txt
135pid1/ 135pid1/
136 bin/ 136 bin/
137 bzImage 137 bzImage
@@ -141,7 +141,7 @@ pid1/
141 141
142## Preparing PID 1 application in Golang 142## Preparing PID 1 application in Golang
143 143
144This step is relatively easy. The only thing we must have in mind that we will 144This step is relatively easy. The only thing we must have in mind that we will
145need to compile the binary as a static one. 145need to compile the binary as a static one.
146 146
147Let's create `init.go` file in the root of the project. 147Let's create `init.go` file in the root of the project.
@@ -162,8 +162,8 @@ func main() {
162} 162}
163``` 163```
164 164
165If you notice, we have a forever loop in the main, with a simple sleep of 1 165If you notice, we have a forever loop in the main, with a simple sleep of 1
166second to not overwhelm the CPU. This is because PID 1 should never complete 166second to not overwhelm the CPU. This is because PID 1 should never complete
167and/or exit. That would result in a kernel panic. Which is BAD! 167and/or exit. That would result in a kernel panic. Which is BAD!
168 168
169There are two ways of compiling Golang application. Statically and dynamically. 169There are two ways of compiling Golang application. Statically and dynamically.
@@ -184,9 +184,9 @@ $ ldd init
184not a dynamic executable 184not a dynamic executable
185``` 185```
186 186
187At this point, we need to create [initramfs](https://www.linuxfromscratch.org/blfs/view/svn/postlfs/initramfs.html) 187At this point, we need to create [initramfs](https://www.linuxfromscratch.org/blfs/view/svn/postlfs/initramfs.html)
188(abbreviated from "initial RAM file system", is the successor of initrd. It 188(abbreviated from "initial RAM file system", is the successor of initrd. It
189is a cpio archive of the initial file system that gets loaded into memory 189is a cpio archive of the initial file system that gets loaded into memory
190during the Linux startup process). 190during the Linux startup process).
191 191
192```sh 192```sh
@@ -196,7 +196,7 @@ $ mv initramfs bin/initramfs
196 196
197The projects at this stage should look like this. 197The projects at this stage should look like this.
198 198
199``` 199```txt
200pid1/ 200pid1/
201 bin/ 201 bin/
202 bzImage 202 bzImage
@@ -209,7 +209,7 @@ pid1/
209## Running all of it with QEMU 209## Running all of it with QEMU
210 210
211[QEMU](https://www.qemu.org/) is a free and open-source hypervisor. It emulates 211[QEMU](https://www.qemu.org/) is a free and open-source hypervisor. It emulates
212the machine's processor through dynamic binary translation and provides a set 212the machine's processor through dynamic binary translation and provides a set
213of different hardware and device models for the machine, enabling it to run a 213of different hardware and device models for the machine, enabling it to run a
214variety of guest operating systems. 214variety of guest operating systems.
215 215
@@ -256,11 +256,11 @@ The whole [log file here](/posts/pid1/qemu.log).
256 256
257## Size comparison 257## Size comparison
258 258
259The cool thing about this approach is that the Linux kernel and the application 259The cool thing about this approach is that the Linux kernel and the application
260together only take around 12 MB, which is impressive as hell. And we need to 260together only take around 12 MB, which is impressive as hell. And we need to
261also know that the size of bzImage (Linux kernel) could be greatly decreased 261also know that the size of bzImage (Linux kernel) could be greatly decreased
262by going into `make menuconfig` and removing a ton of features from the kernel, 262by going into `make menuconfig` and removing a ton of features from the kernel,
263making the size even smaller. I managed to get kernel size down to 2 MB and 263making the size even smaller. I managed to get kernel size down to 2 MB and
264still working properly. 264still working properly.
265 265
266```sh 266```sh
@@ -325,24 +325,23 @@ genisoimage -R \
325 iso 325 iso
326``` 326```
327 327
328This will produce `GoAsPID1.iso` which you can use with [Virtualbox](https://www.virtualbox.org/) 328This will produce `GoAsPID1.iso` which you can use with [Virtualbox](https://www.virtualbox.org/)
329or [Gnome Boxes](https://apps.gnome.org/app/org.gnome.Boxes/). 329or [Gnome Boxes](https://apps.gnome.org/app/org.gnome.Boxes/).
330 330
331<video src="/posts/pid1/boxes.mp4" controls></video> 331<video src="/posts/pid1/boxes.mp4" controls></video>
332 332
333## Is running applications as PID 1 even worth it? 333## Is running applications as PID 1 even worth it?
334 334
335Well, the answer to this is not as simple as one would think. Sometimes it is 335Well, the answer to this is not as simple as one would think. Sometimes it is
336and sometimes it's not. For embedded systems and very specialized applications 336and sometimes it's not. For embedded systems and very specialized applications
337it is worth for sure. But in normal uses, I don't think so. It was an interesting 337it is worth for sure. But in normal uses, I don't think so. It was an interesting
338exercise in compiling kernels and looking at the guts of the Linux kernel, 338exercise in compiling kernels and looking at the guts of the Linux kernel,
339but sticking to containers for most of the things is a better option in my 339but sticking to containers for most of the things is a better option in my
340opinion. 340opinion.
341 341
342An interesting experiment would be creating an image that supports networking 342An interesting experiment would be creating an image that supports networking
343and could be deployed to AWS as an EC2 instance and observing how it fares. 343and could be deployed to AWS as an EC2 instance and observing how it fares.
344But in that case, we would need to write some sort of supervisor that would 344But in that case, we would need to write some sort of supervisor that would
345run on a separate EC2 that would check if other EC2 instances are running 345run on a separate EC2 that would check if other EC2 instances are running
346properly. Remember that if your application fails, kernel panics and the 346properly. Remember that if your application fails, kernel panics and the
347whole machine is inoperable in this case. 347whole machine is inoperable in this case.
348