container/doc/Creating_containers-Part_1....

713 lines
49 KiB
HTML

<!DOCTYPE html>
<html lang="en"><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><script type="text/javascript" async="" src="Creating%20containers%20-%20Part%201_files/ga.js"></script><script src="Creating%20containers%20-%20Part%201_files/analytics.js" type="text/javascript"></script>
<script type="text/javascript">window.addEventListener('DOMContentLoaded',function(){var v=archive_analytics.values;v.service='wb';v.server_name='wwwb-app102.us.archive.org';v.server_ms=1019;archive_analytics.send_pageview({});});</script><script type="text/javascript" src="Creating%20containers%20-%20Part%201_files/playback.js" charset="utf-8"></script>
<script type="text/javascript" src="Creating%20containers%20-%20Part%201_files/wombat.js" charset="utf-8"></script>
<script type="text/javascript">
if (window._WBWombatInit) {
wbinfo = {}
wbinfo.url = "http://crosbymichael.com:80/creating-containers-part-1.html";
wbinfo.timestamp = "20191223021405";
wbinfo.request_ts = "20191223021405";
wbinfo.prefix = "http://web.archive.org/web/";
wbinfo.mod = "if_";
wbinfo.is_framed = false;
wbinfo.is_live = false;
wbinfo.coll = "web";
wbinfo.proxy_magic = "";
wbinfo.static_prefix = "/_static/";
wbinfo.enable_auto_fetch = true;
wbinfo.auto_fetch_worker_prefix = "http://web.archive.org/web/";
wbinfo.wombat_ts = "20191223021405";
wbinfo.wombat_sec = "1577067245";
wbinfo.wombat_scheme = "https";
wbinfo.wombat_host = "crosbymichael.com:80";
wbinfo.ignore_prefixes = ["/__wb/",
"/_static/",
"/web/",
"http://analytics.archive.org/",
"https://analytics.archive.org/",
"//analytics.archive.org/",
"http://archive.org/",
"https://archive.org/",
"//archive.org/",
"http://faq.web.archive.org/",
"http://web.archive.org/",
"https://web.archive.org/"
];
wbinfo.wombat_opts = {};
window._WBWombatInit(wbinfo);
}
__wm.init("http://web.archive.org/web");
</script>
<link rel="stylesheet" type="text/css" href="Creating%20containers%20-%20Part%201_files/banner-styles.css">
<link rel="stylesheet" type="text/css" href="Creating%20containers%20-%20Part%201_files/iconochive.css">
<!-- End Wayback Rewrite JS Include -->
<meta charset="utf-8">
<title>Creating containers - Part 1</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<link rel="stylesheet" href="Creating%20containers%20-%20Part%201_files/main.css" type="text/css">
<link href="Creating%20containers%20-%20Part%201_files/css.css" rel="stylesheet" type="text/css">
<link id="elemento-theme" href="Creating%20containers%20-%20Part%201_files/bootstrap.css" rel="stylesheet">
<link href="http://web.archive.org/web/20191223021405/http://crosbymichael.com/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="Michael Crosby ATOM Feed">
<script src="Creating%20containers%20-%20Part%201_files/jquery_002.js"></script>
<script>
$(document).ready(function() {
$('#walter').on('click', function(e) {
e.stopPropagation();
window.location = 'http://web.archive.org/web/20191223021405/http://docker.io';
});
$('#scene').parallax();
});
</script>
<style type="text/css">
body {
padding-top: 20px;
padding-bottom: 40px;
}
/* Custom container */
.container-narrow {
margin: 0 auto;
max-width: 700px;
}
.container-narrow > hr {
margin: 30px 0;
}
/* Supporting marketing content */
.marketing {
margin: 60px 0;
}
.marketing p + h4 {
margin-top: 28px;
}
#scene {
position:absolute;
top: 0;
right: 0;
}
</style>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-21167181-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'http://web.archive.org/web/20191223021405/https://ssl' : 'http://web.archive.org/web/20191223021405/http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<link href="Creating%20containers%20-%20Part%201_files/bootstrap-responsive.css" rel="stylesheet">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<script src="http://code.onion.com/fartscroll.js"></script>
<script type="text/javascript">
fartscroll(50);
</script>
<![endif]-->
<script type="text/javascript" async="" src="Creating%20containers%20-%20Part%201_files/embed.html"></script></head>
<body><!-- BEGIN WAYBACK TOOLBAR INSERT -->
<style type="text/css">
body {
margin-top:0 !important;
padding-top:0 !important;
/*min-width:800px !important;*/
}
</style>
<div id="wm-ipp-base" style="display: block; direction: ltr;" lang="en">
</div><div id="donato" style="position:relative;width:100%;">
<div id="donato-base">
<iframe id="donato-if" src="Creating%20containers%20-%20Part%201_files/donate.html" scrolling="no" style="width:100%; height:100%" frameborder="0">
</iframe>
</div>
</div><script type="text/javascript">
__wm.bt(625,27,25,2,"web","http://crosbymichael.com/creating-containers-part-1.html","20191223021405",1996,"/_static/",["/_static/css/banner-styles.css?v=HyR5oymJ","/_static/css/iconochive.css?v=qtvMKcIJ"]);
</script>
<!-- END WAYBACK TOOLBAR INSERT -->
<div class="container-narrow">
<div class="masthead">
<ul class="nav nav-pills pull-right">
<li><a href="http://web.archive.org/web/20191223021405/http://crosbymichael.com/feeds/all.atom.xml" rel="alternate">atom feed</a></li>
<li><a href="http://web.archive.org/web/20191223021405/http://github.com/crosbymichael">github</a></li>
</ul>
<h3 class="muted"><a href="http://web.archive.org/web/20191223021405/http://crosbymichael.com/index.html">Michael Crosby</a></h3>
</div> <ul id="scene" style="transform: translate3d(0px, 0px, 0px); transform-style: preserve-3d; backface-visibility: hidden;">
<li class="layer" data-depth="1.0" style="position: relative; display: block; height: 100%; width: 100%; left: 0px; top: 0px; transform: translate3d(-4.55115%, 2.10234%, 0px); transform-style: preserve-3d; backface-visibility: hidden;">
<img src="Creating%20containers%20-%20Part%201_files/docker-logo.png" id="walter">
</li>
</ul>
<hr>
<ul class="nav nav-pills pull-left">
<li><a href="http://web.archive.org/web/20191223021405/http://crosbymichael.com/category/dev.html">dev</a></li>
<li class="active"><a href="http://web.archive.org/web/20191223021405/http://crosbymichael.com/category/docker.html">docker</a></li>
<li><a href="http://web.archive.org/web/20191223021405/http://crosbymichael.com/category/go.html">go</a></li>
<li><a href="http://web.archive.org/web/20191223021405/http://crosbymichael.com/category/productivity.html">productivity</a></li>
<hr>
</ul>
<div class="row-fluid marketing">
<div class="span12">
<section id="content" class="body">
<article>
<header> <h1 class="entry-title"><a href="http://web.archive.org/web/20191223021405/http://crosbymichael.com/creating-containers-part-1.html" rel="bookmark" title="Permalink to Creating containers - Part 1">Creating containers - Part 1</a></h1> </header>
<div class="entry-content">
<footer class="post-info">
<abbr class="published" title="2014-11-16T00:00:00+00:00">
Sun 16 November 2014
</abbr>
<address class="vcard author">
By <a class="url fn" href="http://web.archive.org/web/20191223021405/http://crosbymichael.com/author/Michael%20Crosby.html">Michael Crosby</a>
</address>
<p>In <a href="http://web.archive.org/web/20191223021405/http://crosbymichael.com/category/docker.html">docker</a>. </p>
<p></p></footer><!-- /.post-info --><!-- /.post-info -->
<p>This is part one of a series of blog posts detailing how docker creates containers.
We will dig deep into the various pieces that are stitched together to see what
it takes to make <code>docker run ...</code> awesome.</p>
<h2>First, what is a container?</h2>
<p>I think the various pieces of technology that goes into creating a container is fairly
commonplace. You should have seen a few cool looking charts in various presentations
about Docker where you get a quick "Docker uses namespaces, cgroups, chroot, etc."
to create containers. But why does it take all these pieces to create a contaienr?<br>
Why is it not a simple syscall and it's all done for me?
The fact is that container's don't exist, they are made up. There is no such
thing as a "linux container" in the kernel. A container is a userland concept.</p>
<h2>Namespaces</h2>
<p>In part one I'll talk about how to create Linux namespaces in the context of how they
are used within docker. In later posts we will look into how namespaces are combined
with other features like cgroups and an isolated filesystem to create something useful.</p>
<p>First off we need a high level explanation of what a namespace does and why it's useful.
Basically, a namespace is a scoped view of your underlying Linux system. There are a
few different types of namespaces implemented inside the kernel. As we dig into
each of the different namespaces below you can follow along by running
<code>docker run -it --privileged --net host crosbymichael/make-containers</code>.
This has a few preloaded files and configuration to get your started. Even though we
will be creating namespaces inside an container that docker runs for us, don't let that
trip you up. I opted for this approach as providing a container preloaded with all
the dependencies that you need to run the examples is why we are doing this in the
first place. To make things a little easier, I'm using the <code>--net host</code> flag so that
we are able to see your host's network interfaces within our demo container. This will
be useful in the network examples. We also need to provide the <code>--privilged</code> flag so that
we have the correct permissions to create new namespaces within our container.</p>
<p>If you are interested in what the Dockerfile looks like then here it is:</p>
<div class="highlight"><pre>FROM debian:jessie
RUN apt-get update <span class="o">&amp;&amp;</span> apt-get install -y <span class="se">\</span>
gcc <span class="se">\</span>
vim <span class="se">\</span>
emacs
COPY containers/ /containers/
WORKDIR /containers
CMD <span class="o">[</span><span class="s2">"bash"</span><span class="o">]</span>
</pre></div>
<p>I'll be doing the examples in C, as it's sometimes easier
to explain the lower level details better than the abstractions that Go provides.
So lets start...</p>
<h3>NET Namespace</h3>
<p>The network namespaces provides your own view of the network stack of your system. This
can include your very own <code>localhost</code>. Make sure you are in the <code>crosbymichael/make-containers</code>
and run the command <code>ip a</code> to view all the network interfaces of your host machine.</p>
<div class="highlight"><pre>&gt; ip a
root@development:/containers# ip a
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu <span class="m">65536</span> qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu <span class="m">1500</span> qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:19:ca:f2 brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe19:caf2/64 scope link
valid_lft forever preferred_lft forever
3: eth1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu <span class="m">1500</span> qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:20:84:47 brd ff:ff:ff:ff:ff:ff
inet 192.168.56.103/24 brd 192.168.56.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe20:8447/64 scope link
valid_lft forever preferred_lft forever
4: docker0: &lt;NO-CARRIER,BROADCAST,MULTICAST,UP&gt; mtu <span class="m">1500</span> qdisc noqueue state DOWN group default
link/ether 56:84:7a:fe:97:99 brd ff:ff:ff:ff:ff:ff
inet 172.17.42.1/16 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::5484:7aff:fefe:9799/64 scope link
valid_lft forever preferred_lft forever
</pre></div>
<p>Ok cool, so this is all the network interfaces currently on <strong>my</strong> host system. Yours may look
a little different but you get the idea. Now let's write some code to create a new network
namespace. For this we will write a skeleton of a small C binary that uses the <code>clone</code> syscall. We will
start by using clone to run binaries that are already installed inside our demo container.
The file <code>skeleton.c</code> should be in the working directory of the demo container.
We will use this file as the basis of all our examples. Here is the code incase you don't feel
like running the container right now.</p>
<div class="highlight"><pre><span class="cp">#define _GNU_SOURCE</span>
<span class="cp">#include &lt;stdio.h&gt;</span>
<span class="cp">#include &lt;stdlib.h&gt;</span>
<span class="cp">#include &lt;sched.h&gt;</span>
<span class="cp">#include &lt;sys/wait.h&gt;</span>
<span class="cp">#include &lt;errno.h&gt;</span>
<span class="cp">#define STACKSIZE (1024*1024)</span>
<span class="k">static</span> <span class="kt">char</span> <span class="n">child_stack</span><span class="p">[</span><span class="n">STACKSIZE</span><span class="p">];</span>
<span class="k">struct</span> <span class="n">clone_args</span> <span class="p">{</span>
<span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">;</span>
<span class="p">};</span>
<span class="c1">// child_exec is the func that will be executed as the result of clone</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">child_exec</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">stuff</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">clone_args</span> <span class="o">*</span><span class="n">args</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">clone_args</span> <span class="o">*</span><span class="p">)</span><span class="n">stuff</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">execvp</span><span class="p">(</span><span class="n">args</span><span class="o">-&gt;</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">args</span><span class="o">-&gt;</span><span class="n">argv</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"failed to execvp argments %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
<span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// we should never reach here!</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">clone_args</span> <span class="n">args</span><span class="p">;</span>
<span class="n">args</span><span class="p">.</span><span class="n">argv</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">clone_flags</span> <span class="o">=</span> <span class="n">SIGCHLD</span><span class="p">;</span>
<span class="c1">// the result of this call is that our child_exec will be run in another</span>
<span class="c1">// process returning it's pid</span>
<span class="kt">pid_t</span> <span class="n">pid</span> <span class="o">=</span>
<span class="n">clone</span><span class="p">(</span><span class="n">child_exec</span><span class="p">,</span> <span class="n">child_stack</span> <span class="o">+</span> <span class="n">STACKSIZE</span><span class="p">,</span> <span class="n">clone_flags</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">args</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">pid</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"clone failed WTF!!!! %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// lets wait on our child process here before we, the parent, exits</span>
<span class="k">if</span> <span class="p">(</span><span class="n">waitpid</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"failed to wait pid %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_SUCCESS</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>This is a small C binary that will allow you to run processes
like <code>./a.out ip a</code>. It uses the arguments that you pass on the cli as the arguments to whatever
process you want to use. Don't worry about the specific implementation too much as it's the
changes we will be making that are the interesting aspects. Remember, this will execute the
binary and arguments of whatever program you want, this means if you want to run one of these demos
below and have it spawn a shell session so that you can poke around in your new namespace then go
ahead. It is a great way to explore and inspect these different namespaces at your own pace.
So to get started let's make a copy of this file to start working with the network namespace.</p>
<div class="highlight"><pre>&gt; cp skeleton.c network.c
</pre></div>
<p>Ok, within this file there is a very
special var called <code>clone_flags</code>. This is where most of our changes will happen throughout this
post. Namespaces are primarily controlled via the clone flags. The clone flag for the network
namespace is <code>CLONE_NEWNET</code>. We need to change the line in the file <code>int clone_flags = SIGCHLD;</code> to
<code>int clone_flags = CLONE_NEWNET | SIGCHLD;</code> so that the call to <code>clone</code> creates a new network namespace
for our process. Make this change in <code>network.c</code> then compile and run.</p>
<div class="highlight"><pre>&gt; gcc -o net network.c
&gt; ./net ip a
1: lo: &lt;LOOPBACK&gt; mtu <span class="m">65536</span> qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</pre></div>
<p>The result of this run now looks very different from the first time we ran the <code>ip a</code>
command. We only see a <code>loopback</code> interface in the output. This is because our process
that was created only has a view of its network namespace and not of the host.
And that's it. That is how you create a new network namespace. </p>
<p>Right now this is pretty useless as you don't have any usable
interfaces. Docker uses this new network namespace to setup a <code>veth</code>
interface so that your container has it's own ip address
allocated on a bridge, usually <code>docker0</code>.
We won't go down the path of how to setup interfaces in namespaces at this time. We can save
that for another post.</p>
<p>So now that we know how a network namespace is created lets look at the mount namespace.</p>
<h3>MNT Namespace</h3>
<p>The mount namespace gives you a scoped view of the mounts on your system. It's often
confused with jailing a process inside a <code>chroot</code> or similar. This is not true!
The mount namespaces does not equal a filesystem jail. So the next time you hear someone
say that a container uses the mount namespace to "jail" the process inside it's own root
filesystem you can call bullshit because they don't know what they are talking about. Do it, it's fun :)</p>
<p>Let's start by making a copy of <code>skeleton.c</code> again for our mount related changes.
We can do a quick build
and run to see what our current mount points looks like with the <code>mount</code> command.</p>
<div class="highlight"><pre>&gt; cp skeleton.c mount.c
&gt; gcc -o mount mount.c
&gt; ./mount mount
proc on /proc <span class="nb">type </span>proc <span class="o">(</span>rw,nosuid,nodev,noexec,relatime<span class="o">)</span>
tmpfs on /dev <span class="nb">type </span>tmpfs <span class="o">(</span>rw,nosuid,mode<span class="o">=</span>755<span class="o">)</span>
shm on /dev/shm <span class="nb">type </span>tmpfs <span class="o">(</span>rw,nosuid,nodev,noexec,relatime,size<span class="o">=</span>65536k<span class="o">)</span>
mqueue on /dev/mqueue <span class="nb">type </span>mqueue <span class="o">(</span>rw,nosuid,nodev,noexec,relatime<span class="o">)</span>
devpts on /dev/pts <span class="nb">type </span>devpts <span class="o">(</span>rw,nosuid,noexec,relatime,gid<span class="o">=</span>5,mode<span class="o">=</span>620,ptmxmode<span class="o">=</span>666<span class="o">)</span>
sysfs on /sys <span class="nb">type </span>sysfs <span class="o">(</span>rw,nosuid,nodev,noexec,relatime<span class="o">)</span>
/dev/disk/by-uuid/d3aa2880-c290-4586-9da6-2f526e381f41 on /etc/resolv.conf <span class="nb">type </span>ext4 <span class="o">(</span>rw,relatime,errors<span class="o">=</span>remount-ro,data<span class="o">=</span>ordered<span class="o">)</span>
/dev/disk/by-uuid/d3aa2880-c290-4586-9da6-2f526e381f41 on /etc/hostname <span class="nb">type </span>ext4 <span class="o">(</span>rw,relatime,errors<span class="o">=</span>remount-ro,data<span class="o">=</span>ordered<span class="o">)</span>
/dev/disk/by-uuid/d3aa2880-c290-4586-9da6-2f526e381f41 on /etc/hosts <span class="nb">type </span>ext4 <span class="o">(</span>rw,relatime,errors<span class="o">=</span>remount-ro,data<span class="o">=</span>ordered<span class="o">)</span>
devpts on /dev/console <span class="nb">type </span>devpts <span class="o">(</span>rw,nosuid,noexec,relatime,gid<span class="o">=</span>5,mode<span class="o">=</span>620,ptmxmode<span class="o">=</span>000<span class="o">)</span>
</pre></div>
<p>This is what the mount points look like from within my demo container, yours may look different.
In order to create a new mount namespace we use the flag <code>CLONE_NEWNS</code>. You may notice something may look
weird here. Why is this flag not <code>CLONE_NEWMOUNT</code> or <code>CLONE_NEWMNT</code>? This is because the mount
namespace was the first Linux namespace introduced and the name was just an undersight.
If you write code you will understand that as you start building a feature or an application,
you don't often have a full picture of the end result. Anyway, let's add <code>CLONE_NEWNS</code> to our
<code>clone_flags</code> variable. It should look something like <code>int clone_flags = CLONE_NEWNS | SIGCHLD;</code>.</p>
<p>Lets go ahead and build <code>mount.c</code> again and run the same command.</p>
<div class="highlight"><pre>&gt; cp skeleton.c mount.c
&gt; gcc -o mount mount.c
&gt; ./mount mount
</pre></div>
<p>Nothing changed. Whaaat??? This is because the process that we run inside the new mount
namespace still has a view on <code>/proc</code> of the underlying system. The result is that the
new process sort of <em>inherits</em> a view on the underlying mounts.
There are a few ways that we can prevent this, like using <code>pivot_root</code>, but we will leave
that for an additional post on filesystem jails and how <code>chroot</code> / <code>pivot_root</code> interact with
the container's mount namespace.</p>
<p>However, one way that we can try out our new mount namespace is to, well, mount something.
Let's create a new <code>tmpfs</code> mount in <code>/mytmp</code> for this demo. We will do this mount
in C and continue to run our same <code>mount</code> command as the args to our mount binary. In order to do a mount
<strong>inside</strong> of our mount namespace we need to add the code to the <code>child_exec</code> function,
before the call to <code>execvp</code>. The code within the <code>child_exec</code> function is run inside the newly
created process, i.e., inside our new namespace. The code in <code>child_exec</code> should look like this:</p>
<div class="highlight"><pre><span class="c1">// child_exec is the func that will be executed as the result of clone</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">child_exec</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">stuff</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">clone_args</span> <span class="o">*</span><span class="n">args</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">clone_args</span> <span class="o">*</span><span class="p">)</span><span class="n">stuff</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">mount</span><span class="p">(</span><span class="s">"none"</span><span class="p">,</span> <span class="s">"/mytmp"</span><span class="p">,</span> <span class="s">"tmpfs"</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"failed to mount tmpfs %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
<span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">execvp</span><span class="p">(</span><span class="n">args</span><span class="o">-&gt;</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">args</span><span class="o">-&gt;</span><span class="n">argv</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"failed to execvp argments %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
<span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// we should never reach here!</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>We need to first create a directory <code>/mytmp</code> before we compile and run with the changes above.</p>
<div class="highlight"><pre>&gt; mkdir /mytmp
&gt; gcc -o mount mount.c
&gt; ./mount mount
<span class="c"># cutting out the common output...</span>
none on /mytmp <span class="nb">type </span>tmpfs <span class="o">(</span>rw,relatime<span class="o">)</span>
</pre></div>
<p>I cut out the common output above from the first time I ran <code>mount</code>.<br>
The result is that you should see a new mount point for our <code>tmpfs</code> mount.
Nice! Go ahead and run <code>mount</code> in the current shell just for comparison.<br>
Notice how the <code>tmpfs</code> mount is not displayed? That is because we created the mount inside
our own mount namespace, not in the parent's namespace. </p>
<p>Remember how I said that the mount namepaces does not equal a filesystem jail? Go ahead and run our
<code>./mount</code> binary with the <code>ls</code> command. Everything is there. Now you have proof!</p>
<h3>UTS Namespace</h3>
<p>The next namespace is the UTS namespace that is responsible for system identification. This includes the
<code>hostname</code> and <code>domainname</code>. It allows a container to have it's own hostname independently
from the host system along with other containers. Let's start by making a copy of <code>skeleton.c</code> and running
the <code>hostname</code> command with it.</p>
<div class="highlight"><pre>&gt; cp skeleton.c uts.c
&gt; gcc -o uts uts.c
&gt; ./uts hostname
development
</pre></div>
<p>This should display your system's hostname (<code>development</code> in my case). Like earlier, let's add
the clone flag for the UTS namespace to the <code>clone_flags</code> variable. The flag should be <code>CLONE_NEWUTS</code>.
If you compile and run then you should see the exact same output. This is totally fine.
The values in the UTS namespace are inherited from the <em>parent</em>.
However, within this new namespace, we can change the hostname without it affecting the <em>parent</em> or other
container's that have a separate UTS namespace.</p>
<p>Let's modify the hostname in the <code>child_exec</code> function. To do that, you will need to add
the <code>#include &lt;unistd.h&gt;</code> header to gain access to the <code>sethostname</code> function, as well as
the <code>#include &lt;string.h&gt;</code> header to use <code>strlen</code> needed by <code>sethostname</code>.
The new body of the <code>child_exec</code> function should look like the following:</p>
<div class="highlight"><pre><span class="c1">// child_exec is the func that will be executed as the result of clone</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">child_exec</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">stuff</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">clone_args</span> <span class="o">*</span><span class="n">args</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">clone_args</span> <span class="o">*</span><span class="p">)</span><span class="n">stuff</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span> <span class="n">new_hostname</span> <span class="o">=</span> <span class="s">"myhostname"</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">sethostname</span><span class="p">(</span><span class="n">new_hostname</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">new_hostname</span><span class="p">))</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"failed to execvp argments %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
<span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">execvp</span><span class="p">(</span><span class="n">args</span><span class="o">-&gt;</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">args</span><span class="o">-&gt;</span><span class="n">argv</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"failed to execvp argments %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
<span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// we should never reach here!</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>Ensure that <code>clone_flags</code> within your main look like <code>int clone_flags = CLONE_NEWUTS | SIGCHLD;</code> then
compile and run the binary with the same args. You should now see the value that we set returned
from the <code>hostname</code> command.
To verify that this change did not affect our current shell go ahead and run <code>hostname</code> and make
sure that you have your original value back.</p>
<div class="highlight"><pre>&gt; gcc -o uts uts.c
&gt; ./uts hostname
myhostname
&gt; hostname
development
</pre></div>
<p>Awesome! We are doing good.</p>
<h3>IPC Namespace</h3>
<p>The IPC namespace is used for isolating interprocess communication, things like SysV message queues.
Let's make a copy of <code>skeleton.c</code> for this namespace.</p>
<div class="highlight"><pre>&gt; cp skeleton.c ipc.c
</pre></div>
<p>The way we are going to test the IPC namespace is by creating a message queue on the host, and
ensuring that we cannot see it when we spawn a new process inside it's own IPC namespace.
Let's first create a message queue in our current shell then compile and run our copy of the
skeleton code to view the queue.</p>
<div class="highlight"><pre>&gt; ipcmk -Q
Message queue id: 65536
&gt; gcc -o ipc ipc.c
&gt; ./ipc ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes message
0xfe7f09d1 <span class="m">65536</span> root <span class="m">644</span> <span class="m">0</span> 0
</pre></div>
<p>Without a new IPC namespace you can see the same message queue that was created.
Now let's add the <code>CLONE_NEWIPC</code> flag to our <code>clone_flags</code> var to create a new IPC namespace for
our process.
The <code>clone_flags</code> var should look like <code>int clone_flags = CLONE_NEWIPC | SIGCHLD;</code>.
Recompile and run the same command again:</p>
<div class="highlight"><pre>&gt; gcc -o ipc ipc.c
&gt; ./ipc ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes message
</pre></div>
<p>Done! The child process is now in a new IPC namespace and has completely separate view
and access to message queues.</p>
<h3>PID Namespace</h3>
<p>This one is fun. The PID namespace is a way to carve up the PIDs that one process can view and
interact with. When we create a new PID namespace the first process will get to be the loved PID 1.
If this process exits the kernel kills everyone else within the namespace.
Let's start by making a copy of <code>skeleton.c</code> for our changes.</p>
<div class="highlight"><pre>&gt; cp skeleton.c pid.c
</pre></div>
<p>To create a new PID namespace, we will have to set the <code>clone_flags</code> with <code>CLONE_NEWPID</code>.
The variable should look like <code>int clone_flags = CLONE_NEWPID | SIGCHLD;</code>. Let's test
by running <code>ps aux</code> in our shell and then compile and run our <code>pid.c</code> binary with
the same arguments.</p>
<div class="highlight"><pre>&gt; ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root <span class="m">1</span> 0.0 0.1 <span class="m">20332</span> <span class="m">3388</span> ? Ss 21:50 0:00 bash
root <span class="m">147</span> 0.0 0.1 <span class="m">17492</span> <span class="m">2088</span> ? R+ 22:49 0:00 ps aux
&gt; gcc -o pid pid.c
&gt; ./pid ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root <span class="m">1</span> 0.0 0.1 <span class="m">20332</span> <span class="m">3388</span> ? Ss 21:50 0:00 bash
root <span class="m">153</span> 0.0 0.0 <span class="m">5092</span> <span class="m">728</span> ? S+ 22:50 0:00 ./pid ps aux
root <span class="m">154</span> 0.0 0.1 <span class="m">17492</span> <span class="m">2064</span> ? R+ 22:50 0:00 ps aux
</pre></div>
<p>WTF??? We expected <code>ps aux</code> to be PID 1 or atleast not see any other pids from the parent.
Why is that? <strong>proc</strong>. The process that we spawned still has a view of <code>/proc</code> from the parent, i.e.
<code>/proc</code> mounted on the host system. So how do we fix this? How to we ensure that our new process
can only view pids within it's namespace? We can start by remounting <code>/proc</code>.<br>
Because we will be dealing with mounts, we can take the opportunity to take what we learned from the
MNT namespace and combine it with our PID namespace so that we don't mess with the <code>/proc</code> of our
host system.</p>
<p>We can start by including the clone flag for the mount namespace along side the
clone flag for pid. It should look something like
<code>int clone_flags = CLONE_NEWPID | CLONE_NEWNS | SIGCHLD;</code>. We need to edit the
<code>child_exec</code> function and remount proc. This will be a simple <code>unmount</code> and <code>mount</code> syscall
for the proc filesystem. Because we are creating a new mount namespace we know that this will
not mess up our host system. The result should look like this:</p>
<div class="highlight"><pre>// child_exec is the func that will be executed as the result of clone
static int child_exec<span class="o">(</span>void *stuff<span class="o">)</span>
<span class="o">{</span>
struct clone_args *args <span class="o">=</span> <span class="o">(</span>struct clone_args *<span class="o">)</span>stuff<span class="p">;</span>
<span class="k">if</span> <span class="o">(</span>umount<span class="o">(</span><span class="s2">"/proc"</span>, 0<span class="o">)</span> !<span class="o">=</span> 0<span class="o">)</span> <span class="o">{</span>
fprintf<span class="o">(</span>stderr, <span class="s2">"failed unmount /proc %s\n"</span>,
strerror<span class="o">(</span>errno<span class="o">))</span><span class="p">;</span>
<span class="nb">exit</span><span class="o">(</span>-1<span class="o">)</span><span class="p">;</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span>mount<span class="o">(</span><span class="s2">"proc"</span>, <span class="s2">"/proc"</span>, <span class="s2">"proc"</span>, 0, <span class="s2">""</span><span class="o">)</span> !<span class="o">=</span> 0<span class="o">)</span> <span class="o">{</span>
fprintf<span class="o">(</span>stderr, <span class="s2">"failed mount /proc %s\n"</span>,
strerror<span class="o">(</span>errno<span class="o">))</span><span class="p">;</span>
<span class="nb">exit</span><span class="o">(</span>-1<span class="o">)</span><span class="p">;</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span>execvp<span class="o">(</span>args-&gt;argv<span class="o">[</span>0<span class="o">]</span>, args-&gt;argv<span class="o">)</span> !<span class="o">=</span> 0<span class="o">)</span> <span class="o">{</span>
fprintf<span class="o">(</span>stderr, <span class="s2">"failed to execvp argments %s\n"</span>,
strerror<span class="o">(</span>errno<span class="o">))</span><span class="p">;</span>
<span class="nb">exit</span><span class="o">(</span>-1<span class="o">)</span><span class="p">;</span>
<span class="o">}</span>
// we should never reach here!
<span class="nb">exit</span><span class="o">(</span>EXIT_FAILURE<span class="o">)</span><span class="p">;</span>
<span class="o">}</span>
</pre></div>
<p>Build and run this again to see what happens.</p>
<div class="highlight"><pre>&gt; gcc -o pid pid.c
&gt; ./pid ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root <span class="m">1</span> 0.0 0.0 <span class="m">9076</span> <span class="m">784</span> ? R+ 23:05 0:00 ps aux
</pre></div>
<p>Perfect! Our new PID namespace is now fully operational with the help of the mount namespace!</p>
<h3>USER Namespace</h3>
<p>The last namespace is the user namespace. This namespace is the new kid on the block and allows you to have
users within this namespace that are not equal users outside of the namespace. This is accomplished
via GID and UID mappings. </p>
<p>This one has a simple demo application without specifying a mapping, even though it's a totally
useless demo. If we add the flag <code>CLONE_NEWUSER</code> to our <code>clone_flags</code> then run something like <code>id</code> or <code>ls -la</code> you will notice that we get <code>nobody</code> within the user namespace. This is because the current user is undefined
right now.</p>
<div class="highlight"><pre>&gt; cp skeleton.c user.c
<span class="c"># add the clone flag</span>
&gt; gcc -o user user.c
&gt; ./user ls -la
total 84
drwxr-xr-x <span class="m">1</span> nobody nogroup <span class="m">4096</span> Nov <span class="m">16</span> 23:10 .
drwxr-xr-x <span class="m">1</span> nobody nogroup <span class="m">4096</span> Nov <span class="m">16</span> 22:17 ..
-rwxr-xr-x <span class="m">1</span> nobody nogroup <span class="m">8336</span> Nov <span class="m">16</span> 22:15 mount
-rw-r--r-- <span class="m">1</span> nobody nogroup <span class="m">1577</span> Nov <span class="m">16</span> 22:15 mount.c
-rwxr-xr-x <span class="m">1</span> nobody nogroup <span class="m">8064</span> Nov <span class="m">16</span> 21:52 net
-rw-r--r-- <span class="m">1</span> nobody nogroup <span class="m">1441</span> Nov <span class="m">16</span> 21:52 network.c
-rwxr-xr-x <span class="m">1</span> nobody nogroup <span class="m">8544</span> Nov <span class="m">16</span> 23:05 pid
-rw-r--r-- <span class="m">1</span> nobody nogroup <span class="m">1772</span> Nov <span class="m">16</span> 23:02 pid.c
-rw-r--r-- <span class="m">1</span> nobody nogroup <span class="m">1426</span> Nov <span class="m">16</span> 21:59 skeleton.c
-rwxr-xr-x <span class="m">1</span> nobody nogroup <span class="m">8056</span> Nov <span class="m">16</span> 23:10 user
-rw-r--r-- <span class="m">1</span> nobody nogroup <span class="m">1442</span> Nov <span class="m">16</span> 23:10 user.c
-rwxr-xr-x <span class="m">1</span> nobody nogroup <span class="m">8408</span> Nov <span class="m">16</span> 22:40 uts
-rw-r--r-- <span class="m">1</span> nobody nogroup <span class="m">1694</span> Nov <span class="m">16</span> 22:36 uts.c
</pre></div>
<p>This is a very simple example of the user namespace but you can go much deeper with it. We will
save this for another post but the idea and hopes for the user namespace is that this will allow
us to run as "root" within the container but not as "root" on the host system. Don't forget you
can always change <code>ls -la</code> to <code>bash</code> and have a shell inside the namespace to poke around and learn
more.</p>
<h3>In the end...</h3>
<p>So to recap we went over the mount, network, user, PID, UTS, and IPC Linux namespaces.
The majority of the code that we changed was not much, just adding a flag most of the time.<br>
The "hard work" is mostly managing the interactions between the various kernel subsystems
in order to meet our requirements. Like most
of the descriptions before this, namespaces are just one of the tools that we use to make a
container. I hope the PID example is a glimpse of how we use multiple namespaces together
in order isolate and begin the creation of a container.</p>
<p>In future posts we will go into detail on how we jail the container's processes inside a root filesystem,
aka a docker image, as well as using cgroups and Linux capabilities. By the end we should be able
to pull all these things together to create a container.</p>
<p>Also a thanks to tibor and everyone that helps review my brain dump of a first draft ;)</p>
</div><!-- /.entry-content -->
</article>
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = 'crosbymichael'; // required: replace example with your forum shortname
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="http://web.archive.org/web/20191223021405/http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://web.archive.org/web/20191223021405/http://disqus.com/" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
</section>
</div>
</div>
<hr>
<div class="footer">
<p>© 2014 Michael Crosby</p>
</div>
</div>
<script src="Creating%20containers%20-%20Part%201_files/bootstrap.js"></script>
<script src="Creating%20containers%20-%20Part%201_files/jquery.js"></script>
</body></html>
<!--
FILE ARCHIVED ON 02:14:05 Dec 23, 2019 AND RETRIEVED FROM THE
INTERNET ARCHIVE ON 21:09:12 Jun 24, 2020.
JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE.
ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C.
SECTION 108(a)(3)).
-->
<!--
playback timings (ms):
PetaboxLoader3.resolve: 260.51 (4)
exclusion.robots.policy: 0.342
load_resource: 714.677
esindex: 0.007
exclusion.robots: 0.353
captures_list: 233.362
LoadShardBlock: 205.234 (3)
CDXLines.iter: 14.759 (3)
PetaboxLoader3.datanode: 585.283 (4)
RedisCDXSource: 7.714
-->