466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
|
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
|
+
-
+
-
+
-
+
-
+
|
c=fossil
b=$HOME/containers/$c
r=$b/rootfs
m=/run/containerd/io.containerd.runtime.v2.task/moby
if [ -d "$t" ] && mkdir -p $r
then
docker container start $c
docker container export $c | sudo tar -C $r -xf -
id=$(docker inspect --format="{{.Id}}" $c)
sudo cat $m/$id/config.json |
jq '.root.path = "'$r'"' |
jq '.linux.cgroupsPath = ""' > $b/config.json
fi
```
----
The first several lines list configurables:
* **b**: the path of the exported container, called the “bundle” in OCI
* **`b`**: the path of the exported container, called the “bundle” in OCI
jargon
* **c**: the name of the Docker container you’re bundling up for use
* **`c`**: the name of the Docker container you’re bundling up for use
with `runc`
* **m**: the [moby] directory, both because it’s long and because it’s
* **`m`**: the [moby] directory, both because it’s long and because it’s
been known to change from one version of Docker to the next
* **r**: the path of the directory containing the bundle’s root file
* **`r`**: the path of the directory containing the bundle’s root file
system.
That last doesn’t have to be called `rootfs/`, and it doesn’t have to
live in the same directory as `config.json`, but it is conventional.
Because some OCI tools use those names as defaults, it’s best to follow
suit.
|
508
509
510
511
512
513
514
515
516
517
518
519
520
521
|
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
|
+
+
+
+
+
+
|
2. To make the Docker-managed machine-readable `config.json` more
human-readable, in case there are other things you want changed in
this version of the container. Exposing the `config.json` file like
this means you don’t have to rebuild the container merely to change
a value like a mount point, the kernel capability set, and so forth.
<a id="why-sudo"></a>
We have to do this transformation of `config.json` as the local root
user because it isn’t readable by your normal user. Additionally, that
input file is only available while the container is started, which is
why we ensure that before exporting the container’s rootfs.
With the container exported like this, you can start it as:
```
$ cd /path/to/bundle
$ c=any-name-you-like
$ sudo runc create $c
$ sudo runc start $c
|
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
|
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
|
-
-
+
+
-
-
-
-
+
-
+
+
-
-
+
+
-
+
-
+
+
+
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
|
everything is working properly. It is. Yay!
The remaining commands show shutting the container down and destroying
it, simply to show how these commands change relative to using the
Docker Engine commands. It’s “kill,” not “stop,” and it’s “delete,” not
“rm.”
Beware that if you’re doing this on a remote host, your bundle export
directory on the build host might not be the same as where it ended up
If you want the bundle to run on a remote host, the local and remote
bundle directories likely will not match, as the shell script above
on the remote host. If so, the shell script above will create a broken
bundle because it’s assuming the `mkdir` command should go to the same
directory as the “`rootfs`” value it set in the `config.json` value.
This is a more realistic shell script for that case:
assumes. This is a more realistic shell script for that case:
----
```shell
#!/bin/sh
#!/bin/bash -ex
c=fossil
b=/var/lib/machines/$c
h=my-host.example.com
m=/run/containerd/io.containerd.runtime.v2.task/moby
t=$(mktemp -d /tmp/$c-bundle.XXXXXX)
r=$t/rootfs
if [ -d "$t" ] && mkdir -p $r
if [ -d "$t" ]
then
docker container start $c
docker container export $c | sudo tar -C $r -xf -
docker container export $c > $t/rootfs.tar
id=$(docker inspect --format="{{.Id}}" $c)
sudo cat $m/$id/config.json |
jq '.root.path = "'$r'"' |
jq '.root.path = "'$b/rootfs'"' |
jq '.linux.cgroupsPath = ""' > $t/config.json
scp -r $t $h:tmp
ssh -t $h "{
rsync -av $t/* remotehost:$b
sudo rm -rf $t
mv ./$t/config.json $b &&
sudo tar -C $b/rootfs -xf ./$t/rootfs.tar &&
rm -r ./$t
}"
rm -r $t
fi
```
----
We’ve introduced the “`t`” variable, a temporary directory we populate
locally, then `rsync` across to the remote machine, updating a
*different* bundle directory, `$b`. We’re using the convention for
systemd based machines here, which will play into the [`nspawn`][sdnsp]
alternative below. Even if you aren’t using `nspawn`, it’s a reasonable
place to put containers under the [Linux FHS rules][LFHS].
We’ve introduced two new variables:
* **`h`**: the remote host name
* **`t`**: a temporary bundle directory we populate locally, then
`scp` to the remote machine, where it’s unpacked
We dropped the **`r`** variable because now we have two different
“rootfs” types: the tarball and the unpacked version of that tarball.
To avoid confusing ourselves between these cases, we’ve replaced uses of
`$r` with explicit paths.
You need to be aware that this script uses `sudo` for two different purposes:
1. To read the local `config.json` file out of the `containerd` managed
directory. ([Details above](#why-sudo).)
2. To unpack the bundle onto the remote machine. If you try to get
clever and unpack it locally, then `rsync` it to the remote host to
avoid re-copying files that haven’t changed since the last update,
you’ll find that it fails when it tries to copy device nodes, to
create files owned only by the remote root user, and so forth. If the
container bundle is small, it’s simpler to re-copy and unpack it
fresh each time.
I point that out because it might ask for your password twice: once for
the local sudo command, and once for the remote.
The default for the **`b`** variable is the convention for systemd based
machines, which will play into the [`nspawn`][sdnsp] alternative below.
Even if you aren’t using `nspawn`, it’s a reasonable place to put
containers under the [Linux FHS rules][LFHS].
[ctrd]: https://containerd.io/
[ecg]: https://github.com/opencontainers/runc/pull/3131
[LFHS]: https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard
[jq]: https://stedolan.github.io/jq/
[moby]: https://github.com/moby/moby
[sdnsp]: #nspawn
|