diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2023-10-31 08:38:25 +0100 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2023-10-31 08:38:25 +0100 |
| commit | f59672679db271f6b24a41e215401ab5001ecd96 (patch) | |
| tree | 367e04e449d8b02266b05d37413cfd2ba2a80b3a /content/posts/2021-12-25-running-golang-application-as-pid1.md | |
| parent | 22f5ac8fcec4dbfcc1f5033f18bc4eeca52a747d (diff) | |
| download | mitjafelicijan.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.md | 79 |
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 | |||
| 13 | very intriguing. When you push away all the marketing speak and look at the | 13 | very intriguing. When you push away all the marketing speak and look at the |
| 14 | idea, it makes a lot of sense. | 14 | idea, 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 | ||
| 19 | I really like the explanation from the article | 19 | I 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). |
| 21 | Really worth a read. | 21 | Really worth a read. |
| 22 | 22 | ||
| 23 | If we compare a normal operating system to a unikernel side by side, they would | 23 | If 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 |  | 26 |  |
| 27 | 27 | ||
| 28 | From this image, we can see how the complexity significantly decreases with | 28 | From this image, we can see how the complexity significantly decreases with |
| 29 | the use of Unikernels. This comes with a price, of course. Unikernels are hard | 29 | the use of Unikernels. This comes with a price, of course. Unikernels are hard |
| 30 | to get running and require a lot of work since you don't have an actual proper | 30 | to get running and require a lot of work since you don't have an actual proper |
| 31 | kernel running in the background providing network access and drivers etc. | 31 | kernel running in the background providing network access and drivers etc. |
| 32 | 32 | ||
| 33 | So as a half step to make the stack simpler, I started looking into using | 33 | So as a half step to make the stack simpler, I started looking into using |
| 34 | Linux kernel as a base and going from there. I came across this | 34 | Linux 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) |
| 36 | by [Rob Landley](https://landley.net) and apart from statically compiling the | 36 | by [Rob Landley](https://landley.net) and apart from statically compiling the |
| 37 | application to be run as PID1 there was really no other obstacles. | 37 | application 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. | |||
| 41 | PID 1 is the first process that Linux kernel starts after the boot process. | 41 | PID 1 is the first process that Linux kernel starts after the boot process. |
| 42 | It also has a couple of unique properties that are unique to it. | 42 | It 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 | ||
| 52 | PID 1 is considered as an Init application which takes care of running other | 52 | PID 1 is considered as an Init application which takes care of running other |
| @@ -73,19 +73,19 @@ PPid: 0 | |||
| 73 | ``` | 73 | ``` |
| 74 | 74 | ||
| 75 | As we can see on my machine the process with id of 1 is [systemd](https://systemd.io/) | 75 | As we can see on my machine the process with id of 1 is [systemd](https://systemd.io/) |
| 76 | which is a software suite that provides an array of system components for Linux | 76 | which is a software suite that provides an array of system components for Linux |
| 77 | operating systems. If you look closely you can also see that the `PPid` | 77 | operating 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 |
| 79 | this process doesn't have a parent. | 79 | this 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 | ||
| 83 | Containers are wonderful, but they come with a lot of baggage. And because they | 83 | Containers are wonderful, but they come with a lot of baggage. And because they |
| 84 | are in their nature layered, the images require quite a lot of space and also a | 84 | are in their nature layered, the images require quite a lot of space and also a |
| 85 | lot of additional software to handle them. They are not as lightweight as they | 85 | lot of additional software to handle them. They are not as lightweight as they |
| 86 | seem, and many popular images require 500 MB plus disk space. | 86 | seem, and many popular images require 500 MB plus disk space. |
| 87 | 87 | ||
| 88 | The idea of running this as PID 1 would result in a significantly smaller footprint, | 88 | The idea of running this as PID 1 would result in a significantly smaller footprint, |
| 89 | as we will see later in the post. | 89 | as 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 | ||
| 96 | 1. Compile Linux kernel with the default definitions. | 96 | 1. Compile Linux kernel with the default definitions. |
| 97 | 2. Prepare a Hello World application in Golang that is statically compiled. | 97 | 2. Prepare a Hello World application in Golang that is statically compiled. |
| 98 | 3. Run it with [QEMU](https://www.qemu.org/) and providing Golang application | 98 | 3. 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 | ||
| 101 | For the sake of simplicity we will not be cross-compiling any of it and just | 101 | For the sake of simplicity we will not be cross-compiling any of it and just |
| @@ -122,16 +122,16 @@ $ cd .. | |||
| 122 | At this point we have kernel image that is located in `arch/x86_64/boot/bzImage`. | 122 | At this point we have kernel image that is located in `arch/x86_64/boot/bzImage`. |
| 123 | We will use this in QEMU later. | 123 | We will use this in QEMU later. |
| 124 | 124 | ||
| 125 | To make our lives a bit easier lets move the kernel image to another place. | 125 | To make our lives a bit easier lets move the kernel image to another place. |
| 126 | Lets create a folder `bin/` in the root of our project with `mkdir -p bin`. | 126 | Lets create a folder `bin/` in the root of our project with `mkdir -p bin`. |
| 127 | 127 | ||
| 128 | 128 | ||
| 129 | At this point we can copy `bzImage` to `bin/` folder with | 129 | At 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 | ||
| 132 | The folder structure of this experiment should look like this. | 132 | The folder structure of this experiment should look like this. |
| 133 | 133 | ||
| 134 | ``` | 134 | ```txt |
| 135 | pid1/ | 135 | pid1/ |
| 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 | ||
| 144 | This step is relatively easy. The only thing we must have in mind that we will | 144 | This step is relatively easy. The only thing we must have in mind that we will |
| 145 | need to compile the binary as a static one. | 145 | need to compile the binary as a static one. |
| 146 | 146 | ||
| 147 | Let's create `init.go` file in the root of the project. | 147 | Let's create `init.go` file in the root of the project. |
| @@ -162,8 +162,8 @@ func main() { | |||
| 162 | } | 162 | } |
| 163 | ``` | 163 | ``` |
| 164 | 164 | ||
| 165 | If you notice, we have a forever loop in the main, with a simple sleep of 1 | 165 | If you notice, we have a forever loop in the main, with a simple sleep of 1 |
| 166 | second to not overwhelm the CPU. This is because PID 1 should never complete | 166 | second to not overwhelm the CPU. This is because PID 1 should never complete |
| 167 | and/or exit. That would result in a kernel panic. Which is BAD! | 167 | and/or exit. That would result in a kernel panic. Which is BAD! |
| 168 | 168 | ||
| 169 | There are two ways of compiling Golang application. Statically and dynamically. | 169 | There are two ways of compiling Golang application. Statically and dynamically. |
| @@ -184,9 +184,9 @@ $ ldd init | |||
| 184 | not a dynamic executable | 184 | not a dynamic executable |
| 185 | ``` | 185 | ``` |
| 186 | 186 | ||
| 187 | At this point, we need to create [initramfs](https://www.linuxfromscratch.org/blfs/view/svn/postlfs/initramfs.html) | 187 | At 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 |
| 189 | is a cpio archive of the initial file system that gets loaded into memory | 189 | is a cpio archive of the initial file system that gets loaded into memory |
| 190 | during the Linux startup process). | 190 | during the Linux startup process). |
| 191 | 191 | ||
| 192 | ```sh | 192 | ```sh |
| @@ -196,7 +196,7 @@ $ mv initramfs bin/initramfs | |||
| 196 | 196 | ||
| 197 | The projects at this stage should look like this. | 197 | The projects at this stage should look like this. |
| 198 | 198 | ||
| 199 | ``` | 199 | ```txt |
| 200 | pid1/ | 200 | pid1/ |
| 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 |
| 212 | the machine's processor through dynamic binary translation and provides a set | 212 | the machine's processor through dynamic binary translation and provides a set |
| 213 | of different hardware and device models for the machine, enabling it to run a | 213 | of different hardware and device models for the machine, enabling it to run a |
| 214 | variety of guest operating systems. | 214 | variety 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 | ||
| 259 | The cool thing about this approach is that the Linux kernel and the application | 259 | The cool thing about this approach is that the Linux kernel and the application |
| 260 | together only take around 12 MB, which is impressive as hell. And we need to | 260 | together only take around 12 MB, which is impressive as hell. And we need to |
| 261 | also know that the size of bzImage (Linux kernel) could be greatly decreased | 261 | also know that the size of bzImage (Linux kernel) could be greatly decreased |
| 262 | by going into `make menuconfig` and removing a ton of features from the kernel, | 262 | by going into `make menuconfig` and removing a ton of features from the kernel, |
| 263 | making the size even smaller. I managed to get kernel size down to 2 MB and | 263 | making the size even smaller. I managed to get kernel size down to 2 MB and |
| 264 | still working properly. | 264 | still working properly. |
| 265 | 265 | ||
| 266 | ```sh | 266 | ```sh |
| @@ -325,24 +325,23 @@ genisoimage -R \ | |||
| 325 | iso | 325 | iso |
| 326 | ``` | 326 | ``` |
| 327 | 327 | ||
| 328 | This will produce `GoAsPID1.iso` which you can use with [Virtualbox](https://www.virtualbox.org/) | 328 | This will produce `GoAsPID1.iso` which you can use with [Virtualbox](https://www.virtualbox.org/) |
| 329 | or [Gnome Boxes](https://apps.gnome.org/app/org.gnome.Boxes/). | 329 | or [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 | ||
| 335 | Well, the answer to this is not as simple as one would think. Sometimes it is | 335 | Well, the answer to this is not as simple as one would think. Sometimes it is |
| 336 | and sometimes it's not. For embedded systems and very specialized applications | 336 | and sometimes it's not. For embedded systems and very specialized applications |
| 337 | it is worth for sure. But in normal uses, I don't think so. It was an interesting | 337 | it is worth for sure. But in normal uses, I don't think so. It was an interesting |
| 338 | exercise in compiling kernels and looking at the guts of the Linux kernel, | 338 | exercise in compiling kernels and looking at the guts of the Linux kernel, |
| 339 | but sticking to containers for most of the things is a better option in my | 339 | but sticking to containers for most of the things is a better option in my |
| 340 | opinion. | 340 | opinion. |
| 341 | 341 | ||
| 342 | An interesting experiment would be creating an image that supports networking | 342 | An interesting experiment would be creating an image that supports networking |
| 343 | and could be deployed to AWS as an EC2 instance and observing how it fares. | 343 | and could be deployed to AWS as an EC2 instance and observing how it fares. |
| 344 | But in that case, we would need to write some sort of supervisor that would | 344 | But in that case, we would need to write some sort of supervisor that would |
| 345 | run on a separate EC2 that would check if other EC2 instances are running | 345 | run on a separate EC2 that would check if other EC2 instances are running |
| 346 | properly. Remember that if your application fails, kernel panics and the | 346 | properly. Remember that if your application fails, kernel panics and the |
| 347 | whole machine is inoperable in this case. | 347 | whole machine is inoperable in this case. |
| 348 | |||
