ostree/doc/atomic-upgrades.xml

182 lines
7.5 KiB
XML

<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
<!ENTITY version SYSTEM "../version.xml">
]>
<part id="atomic-upgrades">
<title>Atomic Upgrades</title>
<chapter id="upgrades-intro">
<title>You can turn off the power anytime you want...</title>
<para>
OSTree is designed to implement fully atomic and safe upgrades;
more generally, atomic transitions between lists of bootable
deployments. If the system crashes or you pull the power, you
will have either the old system, or the new one.
</para>
</chapter>
<chapter id="simple-http">
<title>Simple upgrades via HTTP</title>
<para>
First, the most basic model OSTree supports is one where it
replicates pre-generated filesystem trees from a server over
HTTP, tracking exactly one ref, which is stored in the <filename
class='extension'>.origin</filename> file for the deployment.
The command <command>ostree admin upgrade</command> implements
this.
</para>
<para>
To begin a simple upgrade, OSTree fetches the contents of the
ref from the remote server. Suppose we're tracking a ref named
<literal>exampleos/buildmaster/x86_64-runtime</literal>.
OSTree fetches the URL
<literal>http://<replaceable>example.com</replaceable>/repo/refs/exampleos/buildmaster/x86_64-runtime</literal>,
which contains a SHA256 checksum. This determines the tree to
deploy, and <filename class='directory'>/etc</filename> will be
merged from currently booted tree.
</para>
<para>
If we do not have this commit, then, then we perform a pull
process. At present (without static deltas), this involves
quite simply just fetching each individual object that we do not
have, asynchronously. Put in other words, we only download
changed files (zlib-compressed). Each object has its checksum
validated and is stored in <filename
class='directory'>/ostree/repo/objects/</filename>.
</para>
<para>
Once the pull is complete, we have all the objects locally
we need to perform a deployment.
</para>
</chapter>
<chapter id="package-manager">
<title>Upgrades via external tools (e.g. package managers)</title>
<para>
As mentioned in the introduction, OSTree is also designed to
allow a model where filesystem trees are computed on the client.
It is completely agnostic as to how those trees are generated;
they could be computed with traditional packages, packages with
post-deployment scripts on top, or built by developers directly
from revision control locally, etc.
</para>
<para>
At a practical level, most package managers today
(<command>dpkg</command> and <command>rpm</command>) operate
"live" on the currently booted filesystem. The way they could
work with OSTree is instead to take the list of installed
packages in the currently booted tree, and compute a new
filesystem from that. A later chapter describes in more details
how this could work: <xref linkend="adapting-existing"/>.
</para>
<para>
For the purposes of this section, let's assume that we have a
newly generated filesystem tree stored in the repo (which shares
storage with the existing booted tree). We can then move on to
checking it back out of the repo into a deployment.
</para>
</chapter>
<chapter id="deployment-dir">
<title>Assembling a new deployment directory</title>
<para>
Given a commit to deploy, OSTree first allocates a directory for
it. This is of the form <filename
class='directory'>/boot/loader/entries/ostree-<replaceable>osname</replaceable>-<replaceable>checksum</replaceable>.<replaceable>serial</replaceable>.conf</filename>.
The <replaceable>serial</replaceable> is normally 0, but if a
given commit is deployed more than once, it will be incremented.
This is supported because the previous deployment may have
configuration in <filename class='directory'>/etc</filename>
that we do not want to use or overwrite.
</para>
<para>
Now that we have a deployment directory, a 3-way merge is
performed between the (by default) currently booted deployment's
<filename class='directory'>/etc</filename>, its default
configuration, and the new deployment (based on its <filename
class='directory'>/usr/etc</filename>).
</para>
</chapter>
<chapter id="swapping-boot">
<title>Atomically swapping boot configuration</title>
<para>
At this point, a new deployment directory has been created as a
hardlink farm; the running system is untouched, and the
bootloader configuration is untouched. We want to add this deployment
to the "deployment list".
</para>
<para>
To support a more general case, OSTree supports atomic
transitioning between arbitrary sets of deployments, with the
restriction that the currently booted deployment must always be
in the new set. In the normal case, we have exactly one
deployment, which is the booted one, and we want to add the new
deployment to the list. A more complex command might allow
creating 100 deployments as part of one atomic transaction, so
that one can set up an automated system to bisect across them.
</para>
<simplesect id="bootversion">
<title>The bootversion</title>
<para>
OSTree allows swapping between boot configurations by
implementing the "swapped directory pattern" in <filename
class='directory'>/boot</filename>. This means it is a
symbolic link to one of two directories <filename
class='directory'>/ostree/boot.<replaceable>[0|1]</replaceable></filename>.
To swap the contents atomically, if the current version is
<literal>0</literal>, we create <filename
class='directory'>/ostree/boot.1</filename>, populate it with
the new contents, then atomically swap the symbolic link. Finally,
the old contents can be garbage collected at any point.
</para>
</simplesect>
<simplesect id="ostree-bootversion">
<title>The /ostree/boot directory</title>
<para>
However, we want to optimize for the case where we the set of
kernel/initramfs pairs is the same between both the old and
new deployment lists. This happens when doing an upgrade that
does not include the kernel; think of a simple translation
update. OSTree optimizes for this case because on some
systems <filename class='directory'>/boot</filename> may be on
a separate medium such as flash storage not optimized for
significant amounts of write traffic.
</para>
<para>
To implement this, OSTree also maintains the directory
<filename
class='directory'>/ostree/boot.<replaceable>bootversion</replaceable></filename>,
which is a set of symbolic links to the deployment
directories. The <replaceable>bootversion</replaceable> here
must match the version of <filename
class='directory'>/boot</filename>. However, in order to
allow atomic transitions of <emphasis>this</emphasis>
directory, this is also a swapped directory, so just like
<filename class='directory'>/boot</filename>, it has a version
of <literal>0</literal> or <literal>1</literal> appended.
</para>
<para>
Each bootloader entry has a special <literal>ostree=</literal>
argument which refers to one of these symbolic links. This is
parsed at runtime in the initramfs.
</para>
</simplesect>
</chapter>
</part>