<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://blog.17lai.site</id>
    <title>夜法之书 • Posts by &#34;docker&#34; category</title>
    <link href="https://blog.17lai.site" />
    <updated>2022-03-18T06:33:17.000Z</updated>
    <category term="github" />
    <category term="jekyll" />
    <category term="blog" />
    <category term="embeded" />
    <category term="hisilicon" />
    <category term="linux" />
    <category term="driver" />
    <category term="3798m" />
    <category term="mpp" />
    <category term="unf" />
    <category term="kernel" />
    <category term="gitlab" />
    <category term="git" />
    <category term="crack" />
    <category term="hardlink" />
    <category term="bt" />
    <category term="pt" />
    <category term="qnap" />
    <category term="硬盘" />
    <category term="nas" />
    <category term="markdown" />
    <category term="picgo" />
    <category term="wiz" />
    <category term="joplin" />
    <category term="typora" />
    <category term="3G" />
    <category term="4G" />
    <category term="3531a" />
    <category term="移植" />
    <category term="嵌入式" />
    <category term="宝塔" />
    <category term="vps" />
    <category term="建站" />
    <category term="nginx" />
    <category term="apache" />
    <category term="mysql" />
    <category term="docker" />
    <category term="lamp" />
    <category term="php" />
    <category term="tomcat" />
    <category term="registry" />
    <category term="教程" />
    <category term="email" />
    <category term="https" />
    <category term="ssl" />
    <category term="debian" />
    <category term="laptop" />
    <category term="ed2k" />
    <category term="ati" />
    <category term="qbittorrent" />
    <category term="ssd" />
    <category term="hdd" />
    <category term="transmission" />
    <category term="pdca" />
    <category term="5w2h" />
    <category term="smart" />
    <category term="swot" />
    <category term="grow" />
    <category term="okr" />
    <category term="wbs" />
    <category term="启动" />
    <category term="tls" />
    <category term="隐私" />
    <category term="安全" />
    <category term="优化" />
    <category term="gitbook" />
    <category term="emby" />
    <category term="sonarr" />
    <category term="jeckett" />
    <category term="portainer" />
    <category term="mariadb" />
    <category term="note" />
    <category term="mermaid" />
    <category term="webdav" />
    <category term="hexo" />
    <category term="matery" />
    <category term="npm" />
    <category term="web" />
    <category term="http" />
    <category term="css" />
    <category term="js" />
    <category term="ai" />
    <category term="face" />
    <category term="3a" />
    <category term="ae" />
    <category term="image" />
    <category term="ccs" />
    <category term="vim" />
    <category term="ide" />
    <category term="music" />
    <category term="韩红" />
    <category term="thinkpad" />
    <category term="sound" />
    <category term="speaker" />
    <category term="刮削" />
    <category term="musicbrainz" />
    <category term="mp3tag" />
    <category term="tmm" />
    <category term="字幕" />
    <category term="plex" />
    <category term="cmake" />
    <category term="develop" />
    <category term="ipv6" />
    <category term="traefik" />
    <category term="proxy" />
    <category term="swarm" />
    <category term="ubuntu" />
    <category term="vscode" />
    <category term="插件" />
    <category term="编码" />
    <category term="plantuml" />
    <category term="mathjax" />
    <category term="ci/cd" />
    <category term="earthly" />
    <category term="mstream" />
    <category term="selfhost" />
    <category term="中岛美嘉" />
    <category term="node" />
    <category term="jenkins" />
    <category term="shell" />
    <category term="tools" />
    <category term="winrar" />
    <category term="emoji" />
    <category term="isp" />
    <category term="awb" />
    <category term="mywork" />
    <category term="cdn" />
    <category term="seo" />
    <category term="fitness" />
    <category term="健身" />
    <category term="运动" />
    <category term="devops" />
    <category term="k8s" />
    <category term="harbor" />
    <category term="pmbok" />
    <category term="管理" />
    <category term="ipd" />
    <category term="绩效" />
    <category term="drowio" />
    <category term="man" />
    <category term="框架" />
    <category term="图解" />
    <category term="doxygen" />
    <category term="bash" />
    <category term="中医" />
    <category term="西医" />
    <category term="文化历史" />
    <category term="竞品分析" />
    <category term="rss" />
    <category term="rsshub" />
    <category term="社会观察" />
    <category term="知识" />
    <category term="智慧" />
    <category term="os" />
    <category term="自制" />
    <category term="编译器" />
    <category term="C" />
    <category term="compiler" />
    <category term="驱动" />
    <category term="sensor" />
    <category term="故事" />
    <category term="nodeppt" />
    <category term="echarts" />
    <category term="写作" />
    <category term="vercel" />
    <category term="potplayer" />
    <category term="action" />
    <category term="workflow" />
    <category term="概率" />
    <category term="普朗克" />
    <category term="投资" />
    <category term="理财" />
    <category term="金融" />
    <category term="yandex" />
    <category term="source" />
    <category term="hack" />
    <category term="download" />
    <category term="chatgpt" />
    <category term="openai" />
    <category term="calibre" />
    <category term="douban" />
    <category term="book" />
    <category term="不可能三角" />
    <category term="waline" />
    <category term="IPD" />
    <category term="MM方法论" />
    <category term="端到端" />
    <category term="信用卡" />
    <category term="定制服务器" />
    <category term="search" />
    <category term="开源" />
    <category term="读书评鉴" />
    <category term="穿越必备" />
    <category term="易学" />
    <category term="google" />
    <category term="人性" />
    <category term="人格分析" />
    <category term="profile" />
    <category term="tampermonkey" />
    <category term="zhihu" />
    <category term="csdn" />
    <category term="juejin" />
    <category term="webhook" />
    <category term="密码" />
    <category term="bitwarden" />
    <category term="office" />
    <category term="kms" />
    <category term="破解" />
    <category term="健康" />
    <category term="恢复" />
    <category term="养生" />
    <category term="刀郎" />
    <category term="study" />
    <category term="english" />
    <category term="雅思" />
    <category term="umami" />
    <category term="windows" />
    <category term="垃圾清理" />
    <category term="速度优化" />
    <category term="阳历" />
    <category term="阴历" />
    <category term="阴阳和历" />
    <category term="天干" />
    <category term="地支" />
    <category term="热水器" />
    <category term="维护" />
    <category term="汽车" />
    <category term="测速" />
    <category term="nat" />
    <category term="光猫" />
    <category term="ipfs" />
    <category term="zlibrary" />
    <category term="sql" />
    <category term="postgresql" />
    <category term="sqlite" />
    <category term="dns" />
    <category term="smartdns" />
    <category term="dnsmasq" />
    <category term="物理" />
    <category term="广义相对论" />
    <category term="科学" />
    <category term="侠义相对论" />
    <category term="AI" />
    <category term="高薪" />
    <category term="传媒" />
    <category term="高考" />
    <category term="专业" />
    <entry>
        <id>https://blog.17lai.site/posts/90e60aac/</id>
        <title>使用 Shell 脚本实现一个简单 Docker</title>
        <link rel="alternate" href="https://blog.17lai.site/posts/90e60aac/"/>
        <content type="html">&lt;blockquote&gt;
&lt;p&gt;《使用 Shell 脚本实现 Docker》旨在通过一系列的实验使用户对docker的底层技术，如Namespace、CGroups、rootfs、联合加载等有一个感性的认识。在此过程中，我们还将通过Shell脚本一步一步地实现一个简易的docker，以期使读者在使用docker的过程中知其然知其所以然。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们的实验环境为Ubuntu 18.04 64bit，简易docker工程的名字为 &lt;code&gt;docker.sh&lt;/code&gt;，该工程仓库地址如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-markup&#34; data-language=&#34;markup&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-markup&#34;&gt;https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;pandengyang&amp;#x2F;docker.sh.git
https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;appotry&amp;#x2F;docker.sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;《使用 Shell 脚本实现 Docker》目录如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-none&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;1. Namespace
1.1. Namespace简介
1.2. uts namespace
1.2.1. uts namespace简介
1.2.2. docker.sh
1.3. mount namespace
1.3.1. &amp;#x2F;etc&amp;#x2F;mtab、&amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mounts
1.3.2. &amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mountinfo
1.3.3. bind mount
1.3.4. mount namespace简介
1.3.5. docker.sh
1.4. pid namespace
1.4.1. unshare的--fork选项
1.4.2. pid namespace简介
1.4.3. pid嵌套
1.4.4. docker.sh
2. CGroups
2.1. CGroups简介
2.2. 限制内存
2.2.1. 用CGroups限制内存
2.2.2. docker.sh
3. 切换根文件系统
3.1. 根文件系统
3.2. pivot_root
3.3. docker.sh
4. 联合加载
4.1. 联合加载简介
4.2. AUFS
4.3. docker.sh
5. 卷
5.1. 卷简介
5.2. docker.sh
6. 后记&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h2 id=&#34;Namespace&#34;&gt;Namespace&lt;/h2&gt;
&lt;h3 id=&#34;Namespace简介&#34;&gt;Namespace简介&lt;/h3&gt;
&lt;p&gt;传统上，在Linux中，许多资源是全局管理的。例如，系统中的所有进程按照惯例是通过PID标识的，这意味着内核必须管理一个全局的PID列表。而且，所有调用者通过uname系统调用返回的系统相关信息都是相同的。用户id的管理方式类似，即各个用户是通过一个全局唯一的UID标识。&lt;/p&gt;
&lt;p&gt;Namespace是Linux用来隔离上述全局资源的一种方式。把一个或多个进程加入到同一个namespace中后，这些进程只会看到该namespace中的资源。namespace是后来加入到Linux中的，为了兼容之前的全局资源管理方式，Linux为每一种资源准备了一个全局的namespace。Linux中的每一个进程都默认加入了这些全局namespace。&lt;/p&gt;
&lt;p&gt;Linux中的每个进程都有一个/proc/[pid]/ns/目录，里面包含了该进程所属的namespace信息。我们查看一下当前Shell的/proc/[pid]/ns目录，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo ls -l &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns
total 0
lrwxrwxrwx 1 phl phl 0 Jan 22 08:43 cgroup -&amp;gt; cgroup:[4026531835]
lrwxrwxrwx 1 phl phl 0 Jan 22 08:43 ipc -&amp;gt; ipc:[4026531839]
lrwxrwxrwx 1 phl phl 0 Jan 22 08:43 mnt -&amp;gt; mnt:[4026531840]
lrwxrwxrwx 1 phl phl 0 Jan 22 08:43 net -&amp;gt; net:[4026531993]
lrwxrwxrwx 1 phl phl 0 Jan 22 08:43 pid -&amp;gt; pid:[4026531836]
lrwxrwxrwx 1 phl phl 0 Jan 22 08:43 pid_for_children -&amp;gt; pid:[4026531836]
lrwxrwxrwx 1 phl phl 0 Jan 22 08:43 user -&amp;gt; user:[4026531837]
lrwxrwxrwx 1 phl phl 0 Jan 22 08:43 uts -&amp;gt; uts:[4026531838]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;该目录下有很多符号链接，每个符号链接代表一个该进程所属的namespace。用readlink读取这些符号链接可以查看进程所属的namespace id。我们读一下当前Shell所属的uts namespace id，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;uts
uts:[4026531838]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;后文中我们将介绍uts namespace、mount namespace、pid namespace的用法。&lt;/p&gt;
&lt;h3 id=&#34;uts-namespace&#34;&gt;uts namespace&lt;/h3&gt;
&lt;h4 id=&#34;uts-namespace简介&#34;&gt;uts namespace简介&lt;/h4&gt;
&lt;p&gt;uts namespace用于隔离系统的主机名等信息，我们将通过实验学习其用法。在实验过程中，我们采用如下的步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;查看全局uts namespace信息&lt;/li&gt;
&lt;li&gt;新建一个uts namespace，查看其信息并作出修改&lt;/li&gt;
&lt;li&gt;查看全局uts namespace，查看其是否被新建的uts namespace影响到&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于其他namespace，我们也采取类似的步骤进行实验学习。&lt;/p&gt;
&lt;p&gt;首先，我们查看一下全局的hostname及uts namespace id。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ hostname
kernelnewbies

phl@kernelnewbies:~$ sudo readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;uts
uts:[4026531838]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，我们创建一个新的uts namespace，并查看其namespce id。&lt;/p&gt;
&lt;p&gt;在继续之前，需要介绍一个namespace工具unshare。利用unshare我们可以新建一个的namespace，并在新namespace中执行一条命令。unshare执行时需要root权限。unshare的使用方法如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;unshare [options] [program [arguments]]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;执行unshare时，我们可以指定要新建的namespace的类型以及要执行的命令。unshare提供了一系列选项，当指定某个选项时可新建指定的namespace。namespace类型选项如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;–uts创建新的uts namespace&lt;/li&gt;
&lt;li&gt;–mount创建新的mount namespace&lt;/li&gt;
&lt;li&gt;–pid创建新的pid namespace&lt;/li&gt;
&lt;li&gt;–user创建新的user namespace&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;介绍完unshare之后，我们继续之前的实验。我们用unshare创建一个新的uts namespace，并在新的uts namespace中执行/bin/bash命令，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo unshare --uts &amp;#x2F;bin&amp;#x2F;bash
root@kernelnewbies:~#&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;我们用unshare创建了一个新的uts namespace。在新的uts namespace中查看其hostname和namespace id，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~$ hostname
kernelnewbies

root@kernelnewbies:~# readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;uts
uts:[4026532177]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，新uts namespace的id与全局uts namespace的id不一致。这说明/bin/bash已运行在一个新的uts namespace中了。&lt;/p&gt;
&lt;p&gt;我们将新uts namespace的hostname改为dreamland，并强制更新Shell提示符。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~# hostname dreamland
root@kernelnewbies:~# hostname
dreamland

root@kernelnewbies:~# exec &amp;#x2F;bin&amp;#x2F;bash
root@dreamland:~#&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，新uts namespace的hostname的确是被修改了，exec /bin/bash用于强制更新Shell的提示符。&lt;/p&gt;
&lt;p&gt;我们重新打开一个Shell窗口，该Shell位于全局uts namespace中。在新的Shell窗口中查看全局uts namespace id及hostname，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ hostname
kernelnewbies

phl@kernelnewbies:~$ sudo readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;uts
uts:[4026531838]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，我们在新uts namespace中所作的修改并未影响到全局的uts namespace。&lt;/p&gt;
&lt;p&gt;父进程创建子进程时只有提供创建新namespace的标志，才可创建新的namespace，并使子进程处于新的namespace中。默认情况下，子进程与父进程处于相同的namespace中。我们在新的uts namespace中创建一个子进程，然后查看该子进程的uts namespace id，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo unshare --uts &amp;#x2F;bin&amp;#x2F;bash
root@kernelnewbies:~# readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;uts
uts:[4026532305]

root@kernelnewbies:~# bash
root@kernelnewbies:~# readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;uts
uts:[4026532305]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，子进程所属uts namespace的id与其父进程相同。其他namespae与uts namespace类似，子进程与父进程同属一个namespace。&lt;/p&gt;
&lt;h4 id=&#34;docker-sh&#34;&gt;&lt;code&gt;docker.sh&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;有了以上关于&lt;code&gt;uts namespace&lt;/code&gt;的介绍，我们就可以将&lt;code&gt;uts namespace&lt;/code&gt;加入到&lt;code&gt;docker.sh&lt;/code&gt;中了。&lt;code&gt;docker.sh&lt;/code&gt;工程分为两个脚本：&lt;code&gt;docker.sh&lt;/code&gt;和&lt;code&gt;container.sh&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker.sh&lt;/code&gt;用于收集用户输入、调用unshare创建namespace并执行container.sh脚本，docker.sh脚本如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;#!&amp;#x2F;bin&amp;#x2F;bash

usage () &amp;#123;
        echo -e &amp;quot;\033[31mIMPORTANT: Run As Root\033[0m&amp;quot;
        echo &amp;quot;&amp;quot;
        echo &amp;quot;Usage:    docker.sh [OPTIONS]&amp;quot;
        echo &amp;quot;&amp;quot;
        echo &amp;quot;A docker written by shell&amp;quot;
        echo &amp;quot;&amp;quot;
        echo &amp;quot;Options:&amp;quot;
        echo &amp;quot;          -c string       docker command&amp;quot;
        echo &amp;quot;                          (\&amp;quot;run\&amp;quot;)&amp;quot;
        echo &amp;quot;          -m              memory&amp;quot;
        echo &amp;quot;                          (\&amp;quot;100M, 200M, 300M...\&amp;quot;)&amp;quot;
        echo &amp;quot;          -C string       container name&amp;quot;
        echo &amp;quot;          -I string       image name&amp;quot;
        echo &amp;quot;          -V string       volume&amp;quot;
        echo &amp;quot;          -P string       program to run in container&amp;quot;

        return 0
&amp;#125;

if test &amp;quot;$(whoami)&amp;quot; !&amp;#x3D; root
then
        usage
        exit -1
fi

while getopts c:m:C:I:V:P: option
do
        case &amp;quot;$option&amp;quot;
        in
                c) cmd&amp;#x3D;$OPTARG;;
                m) memory&amp;#x3D;$OPTARG;;
                C) container&amp;#x3D;$OPTARG;;
                I) image&amp;#x3D;$OPTARG;;
                V) volume&amp;#x3D;$OPTARG;;
                P) program&amp;#x3D;$OPTARG;;
                \?) usage
                    exit -2;;
        esac
done

export cmd&amp;#x3D;$cmd
export memory&amp;#x3D;$memory
export container&amp;#x3D;$container
export image&amp;#x3D;$image
export volume&amp;#x3D;$volume
export program&amp;#x3D;$program

unshare --uts .&amp;#x2F;container.sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;脚本最开始为usage函数，该函数为docker.sh的使用说明。当用户以非预期的方式使用docker.sh时，该函数会被调用。该函数输出如下信息：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;IMPORTANT: Run As Root

Usage:  docker.sh [OPTIONS]

A docker written by shell

Options:
                -c string       docker command
                                (&amp;quot;run&amp;quot;)
                -m              memory
                                (&amp;quot;100M, 200M, 300M...&amp;quot;)
                -C string       container name
                -I string       image name
                -V string       volume
                -P string       program to run in container&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从usage函数的输出我们可以看到，执行docker.sh时需要root权限且需要正确地传递参数。&lt;/p&gt;
&lt;p&gt;docker.sh首先对当前用户进行检测，如果用户不为root，则打印使用说明并退出脚本；如果用户为root，则继续执行。检测用户的脚本如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;if test &amp;quot;$(whoami)&amp;quot; !&amp;#x3D; root
then
        usage
        exit -1
fi&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，docker.sh使用getopts从命令行提取参数，然后赋值给合适的变量。从命令行提取参数的脚本如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;while getopts c:m:C:I:V:P: option
do
        case &amp;quot;$option&amp;quot;
        in
                c) cmd&amp;#x3D;$OPTARG;;
                m) memory&amp;#x3D;$OPTARG;;
                C) container&amp;#x3D;$OPTARG;;
                I) image&amp;#x3D;$OPTARG;;
                V) volume&amp;#x3D;$OPTARG;;
                P) program&amp;#x3D;$OPTARG;;
                \?) usage
                    exit -2;;
        esac
done&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;如果用户的输入不正确，则打印使用说明并退出脚本；如果用户输入正确，则解析命令行参数并赋值给合适的变量。&lt;/p&gt;
&lt;p&gt;为了简化，用户在运行docker.sh时需提供完整的参数列表，示例如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;sudo .&amp;#x2F;docker.sh -c run -m 100M -C dreamland -I ubuntu1604 -V data1 -P &amp;#x2F;bin&amp;#x2F;bash&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;当然，如果当前用户就是root，就不需要sudo了。下表列出了各个参数的含义及示例：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2022/03/1820220318143125.png&#34; alt=&#34;使用 Shell 脚本实现 Docker&#34;&gt;&lt;/p&gt;
&lt;p&gt;docker.sh将命令行参数赋值给变量后，需要将这些变量导出，以传递给&lt;code&gt;container.sh&lt;/code&gt;。导出变量的脚本如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;export cmd&amp;#x3D;$cmd
export memory&amp;#x3D;$memory
export container&amp;#x3D;$container
export image&amp;#x3D;$image
export volume&amp;#x3D;$volume
export program&amp;#x3D;$program&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;这里说明一下为什么要将docker.sh工程拆分为docker.sh和container.sh两个脚本。因为调用unshare创建新的namespace时，会执行一个命令，该命令在新的namespace中运行。该命令一旦结束，unshare也就结束了，unshare创建的新namespace也就不存在了。&lt;/p&gt;
&lt;p&gt;docker.sh不会并发地执行unshare命令与unshare之后的脚本，因此，只有unshare结束了，后续脚本才可继续运行。但是当unshare结束了，准备执行后续脚本时，新的namespae已经不存在了。因此一些加入cgroups、切换根文件系统等工作必须在unshare执行的命令中进行，所以我们采用在unshare中执行container.sh脚本的方式完成后续的工作。&lt;/p&gt;
&lt;p&gt;最后，docker.sh调用unshare创建新的uts namespace，并执行container.sh脚本。调用unshare的脚本如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;unshare --uts .&amp;#x2F;container.sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;container.sh将容器的hostname修改为通过-C传递的容器的名字，然后执行通过-P传递的程序。container.sh脚本如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;#!&amp;#x2F;bin&amp;#x2F;bash

hostname $container
exec $program&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;现在，我们运行&lt;code&gt;docker.sh&lt;/code&gt;，并查看其hostname。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo .&amp;#x2F;docker.sh -c run -m 100M -C dreamland -I ubuntu1604 -V data1 -P &amp;#x2F;bin&amp;#x2F;bash
root@dreamland:~&amp;#x2F;docker.sh# hostname
dreamland&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，容器的hostname已经改变为我们传递的容器名字dreamland了。&lt;/p&gt;
&lt;h3 id=&#34;mount-namespace&#34;&gt;mount namespace&lt;/h3&gt;
&lt;h4 id=&#34;etc-mtab、-proc-self-mounts&#34;&gt;/etc/mtab、/proc/self/mounts&lt;/h4&gt;
&lt;p&gt;早期的Linux使用/etc/mtab文件来记录当前的挂载点信息。每次mount/umount文件系统时会更新/etc/mtab文件中的信息。&lt;/p&gt;
&lt;p&gt;后来，linux引入了mount namespace，每个进程都有一份自己的挂载点信息。当然，处于同一个mount namespace里面的进程，其挂载点信息是相同的。进程的挂载点信息通过/proc/[pid]/mounts文件导出给用户。&lt;/p&gt;
&lt;p&gt;为了兼容以前的/etc/mtab，/etc/mtab变成了指向/proc/self/mounts的符号链接。通过readlink查看/etc/mtab指向的文件，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ readlink &amp;#x2F;etc&amp;#x2F;mtab
..&amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mounts&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;通过读取/proc/self/mounts文件，可以查看当前的挂载点信息，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ cat &amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mounts
sysfs &amp;#x2F;sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
proc &amp;#x2F;proc proc rw,nosuid,nodev,noexec,relatime 0 0
&amp;#x2F;dev&amp;#x2F;sda1 &amp;#x2F; ext4 rw,relatime,errors&amp;#x3D;remount-ro 0 0
securityfs &amp;#x2F;sys&amp;#x2F;kernel&amp;#x2F;security securityfs rw,nosuid,nodev,noexec,relatime 0 0
...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;由于该文件中内容太多，我们省略了一部分，只保留了一些比较重要的挂载点信息。每行的信息分为六个字段，各字段的含义及示例如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2022/03/1820220318143143.png&#34; alt=&#34;使用 Shell 脚本实现 Docker&#34;&gt;&lt;/p&gt;
&lt;p&gt;由于该文件有点过时，被后文介绍的/proc/self/mountinfo替换掉，所以不做过多介绍。&lt;/p&gt;
&lt;h4 id=&#34;proc-self-mountinfo&#34;&gt;/proc/self/mountinfo&lt;/h4&gt;
&lt;p&gt;/proc/self/mountinfo包含了进程mount namespace中的挂载点信息。 它提供了旧的/proc/[pid]/mounts文件中缺少的各种信息（传播状态，挂载点id，父挂载点id等），并解决了/proc/[pid]/mounts文件的一些其他缺陷。我们查看进程挂载点信息时应优先使用该文件。&lt;/p&gt;
&lt;p&gt;该文件中每一行代表一个挂载点信息，每个挂载点信息分为11个字段。挂载点信息的示例如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2022/03/1820220318143124.png&#34; alt=&#34;使用 Shell 脚本实现 Docker&#34;&gt;&lt;/p&gt;
&lt;p&gt;各字段的含义及示例如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2022/03/1820220318143118.png&#34; alt=&#34;使用 Shell 脚本实现 Docker&#34;&gt;&lt;/p&gt;
&lt;p&gt;我们主要关注可选字段中的传播状态选项。首先，我们看一下关于mount namespace的问题。问题如下：&lt;/p&gt;
&lt;p&gt;当创建mount namespace时，新mount namespace会拷贝一份老mount namespace里面的挂载点信息。例如，全局mount namespace中有一个/a挂载点，新建的mount namespace中也会有一个/a挂载点。那么我们在新mount namespace中的/a下创建或删除一个挂载点，全局mount namespace中的/a会同步创建或删除该挂载点吗？或者在全局mount namespace中的/a下创建或删除一个挂载点，新mount namespace中的/a会同步创建或删除该挂载点吗？&lt;/p&gt;
&lt;p&gt;mountinfo文件中可选字段的传播状态就是控制在一个挂载点下进行创建/删除挂载点操作时是否会传播到其他挂载点的选项。传播状态有四种可取值，常见的有如下两种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;shared 表示创建/删除挂载点的操作会传播到其他挂载点&lt;/li&gt;
&lt;li&gt;private 表示创建/删除挂载点的操作不会传播到其他挂载点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于在容器技术中要保证主机与容器的挂载点信息互不影响，因此要求容器中的挂载点的传播状态为private。&lt;/p&gt;
&lt;h4 id=&#34;1-3-3-bind-mount&#34;&gt;1.3.3.bind mount&lt;/h4&gt;
&lt;p&gt;bind mount可以将一个目录（源目录）挂载到另一个目录（目的目录），在目的目录里面的读写操作将直接作用于源目录。&lt;/p&gt;
&lt;p&gt;下面我们通过实验了解一下bind mount的功能，首先，我们准备一下实验所需要的的目录及文件。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ mkdir bind
phl@kernelnewbies:~$ cd bind&amp;#x2F;
phl@kernelnewbies:~&amp;#x2F;bind$ mkdir a
phl@kernelnewbies:~&amp;#x2F;bind$ mkdir b
phl@kernelnewbies:~&amp;#x2F;bind$ echo hello, a &amp;gt; a&amp;#x2F;a.txt
phl@kernelnewbies:~&amp;#x2F;bind$ echo hello, b &amp;gt; b&amp;#x2F;b.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，我们将a目录bind mount到b目录并查看b目录下的内容。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;bind$ sudo mount --bind a b
phl@kernelnewbies:~&amp;#x2F;bind$ tree b
b
└── a.txt
0 directories, 1 file&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，b目录下原先的内容被隐藏，取而代之的是a目录下的内容。&lt;/p&gt;
&lt;p&gt;然后，我们修改b目录下的内容，修改完毕后，从b目录上卸载掉a目录。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;bind$ echo hello, a from b &amp;gt; b&amp;#x2F;a.txt
phl@kernelnewbies:~&amp;#x2F;bind$ sudo umount b&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;我们读取一下a目录中a.txt，看看其内容是否被改变。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;bind$ cat a&amp;#x2F;a.txt
hello, a from b&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，a目录中的内容确实被当a被bind mount到b时对b目录的操作所修改了。&lt;/p&gt;
&lt;p&gt;bind mount在容器技术中有很重要的用途，后文会有涉及。&lt;/p&gt;
&lt;h4 id=&#34;mount-namespace简介&#34;&gt;mount namespace简介&lt;/h4&gt;
&lt;p&gt;mount namespace用来隔离文件系统的挂载点信息, 使得不同的mount namespace拥有自己独立的挂载点信息。不同的namespace之间不会相互影响，其在unshare中的选项为–mount。&lt;/p&gt;
&lt;p&gt;当用unshare创建新的mount namespace时，新创建的namespace将拷贝一份老namespace里的挂载点信息，但从这之后，他们就没有关系了。这是unshare将新 namespace 里面的所有挂载点的传播状态设置为private实现的。通过mount和umount增加和删除各自mount namespace里面的挂载点都不会相互影响。&lt;/p&gt;
&lt;p&gt;下面我们将演示mount namespace的用法。首先，我们准备需要的目录和文件，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ mkdir -p hds&amp;#x2F;hd1 hds&amp;#x2F;hd2 &amp;amp;&amp;amp; cd hds

phl@kernelnewbies:~&amp;#x2F;hds$ dd if&amp;#x3D;&amp;#x2F;dev&amp;#x2F;zero bs&amp;#x3D;1M count&amp;#x3D;1 of&amp;#x3D;hd1.img &amp;amp;&amp;amp; mkfs.ext2 hd1.img
phl@kernelnewbies:~&amp;#x2F;hds$ dd if&amp;#x3D;&amp;#x2F;dev&amp;#x2F;zero bs&amp;#x3D;1M count&amp;#x3D;1 of&amp;#x3D;hd2.img &amp;amp;&amp;amp; mkfs.ext2 hd2.img

phl@kernelnewbies:~$ tree .
.
├── hd1
├── hd1.img
├── hd2
└── hd2.img
2 directories, 2 files&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，我们在全局的mount namespace中挂载hd1.img到hd1目录，然后查看该mount namespace中的挂载点信息与mount namespace id。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;hds$ sudo mount hd1.img hd1
phl@kernelnewbies:~&amp;#x2F;hds$ cat &amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mountinfo | grep hd
556 27 7:18 &amp;#x2F; &amp;#x2F;home&amp;#x2F;phl&amp;#x2F;hds&amp;#x2F;hd1 rw,relatime shared:372 - ext2 &amp;#x2F;dev&amp;#x2F;loop18 rw

phl@kernelnewbies:~&amp;#x2F;hds$ sudo readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;mnt
mnt:[4026531840]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，执行unshare命令创建一个新的mount namespace并查看该mount namespace id和挂载点信息。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;hds$ sudo unshare --uts --mount &amp;#x2F;bin&amp;#x2F;bash
root@kernelnewbies:~&amp;#x2F;hds# cat &amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mountinfo | grep hd
739 570 7:18 &amp;#x2F; &amp;#x2F;home&amp;#x2F;phl&amp;#x2F;hds&amp;#x2F;hd1 rw,relatime - ext2 &amp;#x2F;dev&amp;#x2F;loop18 rw

root@kernelnewbies:~&amp;#x2F;hds# readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;mnt
mnt:[4026532180]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，新mount namespace中的挂载点信息与全局mountnamespace中的挂载点信息基本一致，一些挂载选项（如传播状态）变化了。新的mount namespace id与全局mount namespace id是不一样的。&lt;/p&gt;
&lt;p&gt;然后，我们在新的mount namespace中挂载hd2.img到hd2目录，并查看挂载点信息。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~&amp;#x2F;hds# mount hd2.img hd2
root@kernelnewbies:~&amp;#x2F;hds# cat &amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mountinfo | grep hd
739 570 7:18 &amp;#x2F; &amp;#x2F;home&amp;#x2F;phl&amp;#x2F;hds&amp;#x2F;hd1 rw,relatime - ext2 &amp;#x2F;dev&amp;#x2F;loop18 rw
740 570 7:19 &amp;#x2F; &amp;#x2F;home&amp;#x2F;phl&amp;#x2F;hds&amp;#x2F;hd2 rw,relatime - ext2 &amp;#x2F;dev&amp;#x2F;loop19 rw&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，新mount namespace中有hd1和hd2这两个挂载点。现在启动一个新的Shell窗口，查看全局mount namespace中的挂载点信息。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;hds$ cat &amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mountinfo | grep hd
556 27 7:18 &amp;#x2F; &amp;#x2F;home&amp;#x2F;phl&amp;#x2F;hds&amp;#x2F;hd1 rw,relatime shared:372 - ext2 &amp;#x2F;dev&amp;#x2F;loop18 rw&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，全局mount namespace中的挂载点信息只有hd1，而没有hd2。这说明在新mount namespace中进行挂载/卸载操作不会影响其他mount namespace中的挂载点信息。&lt;/p&gt;
&lt;p&gt;mount namespace只隔离挂载点信息，并不隔离挂载点下面的文件信息。对于多个mount namespace都能看到的挂载点，如果在一个namespace中修改了挂载点下面的文件，其他namespace也能感知到。下面，我们在新建的mount namespace中创建一个文件，命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~&amp;#x2F;hds# echo hello from new mount namespace &amp;gt; hd1&amp;#x2F;hello.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;在新启动的Shell中，查看hd1目录并读取hd1/hello.txt文件。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;hds$ tree hd1
hd1
├── hello.txt
└── lost+found [error opening dir]
1 directory, 1 file

phl@kernelnewbies:~&amp;#x2F;hds$ cat hd1&amp;#x2F;hello.txt
hello from new mount namespace&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，在全局mount namespace中，我们可以读取到在新建的mount namespace中创建的文件。&lt;/p&gt;
&lt;h4 id=&#34;docker-sh-2&#34;&gt;&lt;code&gt;docker.sh&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;有了以上关于mount namespace的知识，我们就可以将mount namespace加入到docker.sh中了。mount namespace将放在docker.sh中，带下划线的行是我们为实现mount namespace而修改的代码。修改后的docker.sh脚本如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;...
unshare --uts --mount .&amp;#x2F;container.sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从上述代码我们可以看到，我们仅仅是在调用unshare时加入–mount选项，就可为docker.sh引入了mount namespace功能。&lt;/p&gt;
&lt;h3 id=&#34;pid-namespace&#34;&gt;pid namespace&lt;/h3&gt;
&lt;h4 id=&#34;unshare的–fork选项&#34;&gt;unshare的–fork选项&lt;/h4&gt;
&lt;p&gt;unshare有一个选项–fork，当执行unshare时，如果没有这个选项，unshare会直接exec新命令，也就是说unshare变成了新命令。如果带有–fork选项，unshare会fork一个子进程，该子进程exec新命令，unshare是该子进程的父进程。我们分别不带–fork和带–fork来执行unshare，然后查看进程之间的关系。&lt;/p&gt;
&lt;p&gt;首先，我们不带–fork选项执行unshare，并查看当前Shell的进程id。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo unshare --uts &amp;#x2F;bin&amp;#x2F;bash
root@kernelnewbies:~&amp;#x2F;hds# echo $$
11699&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;此时unshare会创建一个新的uts namespace，然后exec /bin/bash。我们启动一个新Shell，然后使用pstree查看进程间关系，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;hds$ pstree -p | grep 11699
sudo(11698)---bash(11699)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，sudo fork出一个子进程，该子进程执行unshare。unshare创建了新uts namespace后，exec了/bin/bash，也就是说unshare变成了/bin/bash。&lt;/p&gt;
&lt;p&gt;然后，我们带–fork选项执行unshare，并查看当前Shell的进程id。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;hds$ sudo unshare --uts --fork &amp;#x2F;bin&amp;#x2F;bash
root@kernelnewbies:~&amp;#x2F;hds# echo $$
11866&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;此时unshare会创建一个新的uts namespace，然后fork出一个子进程，该子进程exec /bin/bash。我们启动一个新Shell，然后使用pstree查看进程间关系，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;hds$ pstree -p | grep 11866
sudo(11864)---unshare(11865)---bash(11866)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，sudo fork出一个子进程，该子进程执行命令unshare。unshare创建了新uts namespace后，fork出一个子进程，该子进程exec /bin/bash，也就是说unshare变成了新的/bin/bash进程的父进程。&lt;/p&gt;
&lt;h4 id=&#34;pid-namespace简介&#34;&gt;pid namespace简介&lt;/h4&gt;
&lt;p&gt;pid namespace用来隔离进程pid空间，使得不同pid namespace里的进程 pid可以重复且相互之间不影响。进程所属的pid namespace在创建的时候就确定了，无法更改，因此需要–fork选项来创建一个新进程，然后将该新进程加入新建的pid namespace中。pid namespace在unshare中的选项为–pid。&lt;/p&gt;
&lt;p&gt;unshare在创建pid namespace时需同时提供–pid与–fork选项。unshare本身会加入全局的pid namespace，其fork出的子进程会加入新建的pid namespace。&lt;/p&gt;
&lt;p&gt;首先，我们查看全局pid namespace id，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;pid
pid:[4026531836]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，执行unshare命令创建一个新的pid namespace并查看该pid namespace id。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo unshare --mount --pid --fork &amp;#x2F;bin&amp;#x2F;bash
root@kernelnewbies:~# readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;pid
pid:[4026531836]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，新创建的进程也处于全局pid namespace中，而不是新的pid namespace。&lt;/p&gt;
&lt;p&gt;出现这种情形是因为当前的/proc文件系统是老的。我们查看一下$$的值，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~# echo $$
1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，$$的值为1，但是/proc文件系统却是老的，因此我们查看的实际是init进程所属的pid namespace，当然是全局pid namespace了。&lt;/p&gt;
&lt;p&gt;重新挂载/proc文件系统，这也是unshare执行时带–mount选项的原因，只有这样，重新挂载/proc文件系统时，不会搞乱整个系统。再次查看新进程所属的pid namespace，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~# mount -t proc proc &amp;#x2F;proc
root@kernelnewbies:~# readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;pid
pid:[4026532182]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，新进程的pid namespace与全局pid namespace的id不同。&lt;/p&gt;
&lt;p&gt;接下来，我们再来查看一下新pid namespace中的进程信息。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 19:03 pts&amp;#x2F;1    00:00:00 &amp;#x2F;bin&amp;#x2F;bash
root        10     1  0 19:03 pts&amp;#x2F;1    00:00:00 ps -e&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，当前pid namespace中只有2个进程，看不到全局pid namespace里面的其他进程。我们通过unshare执行的进程pid为1，也就是说该进程成了新pid namespace中的init进程。&lt;/p&gt;
&lt;h4 id=&#34;pid嵌套&#34;&gt;pid嵌套&lt;/h4&gt;
&lt;p&gt;pid namespace可以嵌套，也就是说有父子关系，在当前pid namespace里面创建的所有新的pid namespace都是当前pid namespace的子pid namespace。&lt;/p&gt;
&lt;p&gt;首先，我们创建3个嵌套的pid namespace，并查看每个pid namespace id。–mount-proc选项用于自动挂载/proc文件系统，省去了手动挂载/proc文件系统的操作。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;pid
pid:[4026531836]

phl@kernelnewbies:~$ sudo unshare --uts --mount --pid --mount-proc --fork &amp;#x2F;bin&amp;#x2F;bash
root@kernelnewbies:~# readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;pid
pid:[4026532182]

root@kernelnewbies:~# unshare --uts --mount --pid --mount-proc --fork &amp;#x2F;bin&amp;#x2F;bash
root@kernelnewbies:~# readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;pid
pid:[4026532185]

root@kernelnewbies:~# unshare --uts --mount --pid --mount-proc --fork &amp;#x2F;bin&amp;#x2F;bash
root@kernelnewbies:~# readlink &amp;#x2F;proc&amp;#x2F;$$&amp;#x2F;ns&amp;#x2F;pid
pid:[4026532188]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，我们启动一个新Shell，然后使用pstree查看进程间关系。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ pstree -lp | grep unshare
sudo(12547)---unshare(12548)---bash(12549)---unshare(12579)---bash(12580)---unshare(12593)---bash(12594)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;使用cat /proc/[pid]/status | grep NSpid可查看某进程在当前pid namespace及子孙pid namespace中的pid。我们在全局pid namespace中查看上述各进程在各pid namespace中的pid，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ cat &amp;#x2F;proc&amp;#x2F;12594&amp;#x2F;status | grep NSpid
NSpid: 12594 21 11 1

phl@kernelnewbies:~$ cat &amp;#x2F;proc&amp;#x2F;12593&amp;#x2F;status | grep NSpid
NSpid: 12593 20 10

phl@kernelnewbies:~$ cat &amp;#x2F;proc&amp;#x2F;12580&amp;#x2F;status | grep NSpid
NSpid: 12580 11 1

phl@kernelnewbies:~$ cat &amp;#x2F;proc&amp;#x2F;12579&amp;#x2F;status | grep NSpid
NSpid: 12579 10

phl@kernelnewbies:~$ cat &amp;#x2F;proc&amp;#x2F;12549&amp;#x2F;status | grep NSpid
NSpid: 12549 1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;下面我们将以上进程在各pid namespace中的pid，整理成表格。表格信息如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2022/03/1820220318143156.png&#34; alt=&#34;使用 Shell 脚本实现 Docker&#34;&gt;&lt;/p&gt;
&lt;p&gt;我们以最后一行为例进行介绍，最后一行有4个pid，这4个pid其实是同一个进程。这个进程在4个pid namespace中都可以被看到，且其在4个pid namespace中的pid各不相同。&lt;/p&gt;
&lt;h4 id=&#34;docker-sh-3&#34;&gt;&lt;code&gt;docker.sh&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;有了以上关于pid namespace的知识，我们就可以将pid namespae加入到docker.sh中了。pid namespace将放在docker.sh中，带下划线的行是我们为实现pid namespace而修改的代码。修改后的docker.sh脚本如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;...
unshare --uts --mount --pid --fork .&amp;#x2F;container.sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从上述代码我们可以看到，我们仅仅是在调用unshare时加入–pid和–fork选项，就可为docker.sh引入了pid namespace功能。&lt;/p&gt;
&lt;p&gt;然后，我们需要重新挂载/proc文件系统。重新挂载/proc文件系统的功能将放在container.sh中，带下划线的行是我们为重新挂载/proc文件系统而新添的代码。修改后的container.sh脚本如下如下所示：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;hostname $container
mount -t proc proc &amp;#x2F;proc
exec $program&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;现在，我们运行&lt;code&gt;docker.sh&lt;/code&gt;，并查看当前的进程信息。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo .&amp;#x2F;docker.sh -c run -m 100M -C dreamland -I ubuntu1604 -V data1 -P &amp;#x2F;bin&amp;#x2F;bash
root@dreamland:~&amp;#x2F;docker.sh# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 17:31 pts&amp;#x2F;1    00:00:00 &amp;#x2F;bin&amp;#x2F;bash
root        16     1  0 17:31 pts&amp;#x2F;1    00:00:00 ps -ef&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可看出，当前进程只有两个，不再有主机上的其他进程。&lt;/p&gt;
&lt;h2 id=&#34;CGroups&#34;&gt;CGroups&lt;/h2&gt;
&lt;h3 id=&#34;CGroups简介&#34;&gt;CGroups简介&lt;/h3&gt;
&lt;p&gt;CGroups是一种将进程分组，并以组为单位对进程实施资源限制的技术。每个组都包含以下几类信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;进程列表&lt;/li&gt;
&lt;li&gt;资源A限制&lt;/li&gt;
&lt;li&gt;资源B限制&lt;/li&gt;
&lt;li&gt;资源C限制&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们将以常见的CPU资源及内存资源为例进行介绍。以下的信息将使进程号为1001、1002、2008、3306的四个进程总共只能使用一个CPU核心；总共最多使用25%的CPU资源；总共最多使用100M内存，这样的一个分组被称为cgroup。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2022/03/1820220318143103.png&#34; alt=&#34;使用 Shell 脚本实现 Docker&#34;&gt;&lt;/p&gt;
&lt;p&gt;上面的介绍只是说明了要将何种资源限制施加于哪些进程，并未说明资源限制是如何施加到进程上。具体施加资源限制的过程需要subsystem来帮忙。subsystem读取cgroup中的资源限制和进程列表，然后将这些资源限制施加到这些进程上。常见的subsystem包括如下几种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cpu&lt;/li&gt;
&lt;li&gt;memory&lt;/li&gt;
&lt;li&gt;pids&lt;/li&gt;
&lt;li&gt;devices&lt;/li&gt;
&lt;li&gt;blkio&lt;/li&gt;
&lt;li&gt;net_cls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每个subsystem只读取与其相关的资源限制，然后施加到进程上。例如：memory子系统只读取内存限制，而cpu子系统只读取cpu限制。&lt;/p&gt;
&lt;p&gt;cgroup被组织成树，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2022/03/1820220318143057.png&#34; alt=&#34;使用 Shell 脚本实现 Docker&#34;&gt;&lt;/p&gt;
&lt;p&gt;采用树状结构可以方便地实现资源限制继承，一个cgroup中的资源限制将作用于该cgroup及其子孙cgroup中的进程。例如：图中13001、10339、2999受到A、B、C、D四个cgroup中的资源限制。这样的一个树状结构被称为hierarchy。&lt;/p&gt;
&lt;p&gt;hierarchy中包含了系统中所有的进程，它们分布于各个cgroup中。在hierarchy中，一个进程必须属于且只属于一个cgroup，这样才能保证对进程施加的资源限制不会遗漏也不会冲突。&lt;/p&gt;
&lt;p&gt;要想让一个subsystem读取hierarchy中各cgroup的资源限制，并施加于其中的进程需要将subsystem和hierarchy关联起来。subsystem与hierarchy的关系如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统中可以有多个hierarchy&lt;/li&gt;
&lt;li&gt;一个hierarchy可以关联0个或多个subsystem，当关联0个subsystem时，该hierarchy只是对进程进行分类&lt;/li&gt;
&lt;li&gt;一个subsystem最多关联到一个hierarchy，因为每个hierarchy都包含系统中所有的进程，若一个subsystem关联到了多个hierarchy，对同一进程将有多种资源限制，这是不对的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;系统使用CGroups通常有两种形式：一种是创建一个hierarchy，将所有的subsystem关联到其上，在这个hierarchy上配置各种资源限制；另一种是为每一个subsystem创建一个hierarchy，并将该subsystem关联到其上，每个hierarchy只对一种资源进行限制。后一种比较清晰，得到了更普遍的采用。&lt;/p&gt;
&lt;p&gt;CGroups不像大多数的技术那样提供API或命令之类的用户接口，而是提供给用户一个虚拟文件系统，该虚拟文件系统类型为cgroup。一个挂载后的cgroup文件系统就是一个hierarchy，文件系统中的一个目录就是一个cgroup，目录中的文件代表了进程列表或者资源限制信息。文件系统是树状结构，其各个目录之间的父子关系就代表了cgroup之间的继承关系。挂载cgroup虚拟文件系统后，通过在该文件系统上创建目录、写进程列表文件、写资源限制文件就可以操作CGroups。&lt;/p&gt;
&lt;p&gt;下面，我们通过实验学习一下CGroups的用法。首先，我们挂载一个cgroup虚拟文件系统，该文件系统不与任何subsystem关联，仅仅是将进程进行分类。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ mkdir -p cg&amp;#x2F;test
# -o none,name&amp;#x3D;test 表示该cgroup文件系统不与任何子系统关联
# 该文件系统用name&amp;#x3D;test来标识
phl@kernelnewbies:~$ sudo mount -t cgroup -o none,name&amp;#x3D;test test cg&amp;#x2F;test
phl@kernelnewbies:~$ tree cg&amp;#x2F;test
cg&amp;#x2F;test
├── cgroup.clone_children
├── cgroup.procs
├── cgroup.sane_behavior
├── notify_on_release
├── release_agent
└── tasks
0 directories, 6 files&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;挂载cgroup文件系统后，该cgroup文件系统的根目录下会生成许多文件，该根目录被称为root cgroup。cgroup.procs里面存放的是当前cgroup中的所有进程id，由于该hierarchy中只有一个cgroup，所以这个文件包含了系统中所有的进程id。其他的文件与cgroups基本功能关系不大，暂时可以忽略。&lt;/p&gt;
&lt;p&gt;在cgroup文件系统中，创建一个目录就会创建一个cgroup。下面我们将会演示如何创建下面这样的hierarchy：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2022/03/1820220318143054.png&#34; alt=&#34;使用 Shell 脚本实现 Docker&#34;&gt;&lt;/p&gt;
&lt;p&gt;命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo mkdir -p cg&amp;#x2F;test&amp;#x2F;test1&amp;#x2F;test11
phl@kernelnewbies:~$ sudo mkdir -p cg&amp;#x2F;test&amp;#x2F;test2&amp;#x2F;test22
phl@kernelnewbies:~$ tree cg&amp;#x2F;test
cg&amp;#x2F;test
├── cgroup.clone_children
├── cgroup.procs
├── cgroup.sane_behavior
├── notify_on_release
├── release_agent
├── tasks
├── test1
│   ├── cgroup.clone_children
│   ├── cgroup.procs
│   ├── notify_on_release
│   ├── tasks
│   └── test11
│       ├── cgroup.clone_children
│       ├── cgroup.procs
│       ├── notify_on_release
│       └── tasks
└── test2
    ├── cgroup.clone_children
    ├── cgroup.procs
    ├── notify_on_release
    ├── tasks
    └── test22
        ├── cgroup.clone_children
        ├── cgroup.procs
        ├── notify_on_release
        └── tasks

4 directories, 22 files&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，我们创建了相应的目录后，这些目录下自动出现了包含cgroup信息的目录及文件。&lt;/p&gt;
&lt;p&gt;删除cgroup时只需删除该cgroup所在的目录即可。下面我们将删除test11 cgroup，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo rmdir cg&amp;#x2F;test&amp;#x2F;test1&amp;#x2F;test11
phl@kernelnewbies:~$ tree cg&amp;#x2F;test
cg&amp;#x2F;test
├── cgroup.clone_children
├── cgroup.procs
├── cgroup.sane_behavior
├── notify_on_release
├── release_agent
├── tasks
├── test1
│   ├── cgroup.clone_children
│   ├── cgroup.procs
│   ├── notify_on_release
│   └── tasks
└── test2
    ├── cgroup.clone_children
    ├── cgroup.procs
    ├── notify_on_release
    ├── tasks
    └── test22
        ├── cgroup.clone_children
        ├── cgroup.procs
        ├── notify_on_release
        └── tasks

3 directories, 18 files&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;每个cgroup下面都有一个cgroup.procs文件，该文件里面包含当前cgroup里面的所有进程id。只要将某个进程的id写入该文件，即可将该进程加入到该cgroup中。下面，我们将当前的bash加入到test22 cgroup中，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ echo $$
3894
phl@kernelnewbies:~$ sudo sh -c &amp;quot;echo 3894 &amp;gt; cg&amp;#x2F;test&amp;#x2F;test2&amp;#x2F;test22&amp;#x2F;cgroup.procs&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;/proc/[pid]/cgroup包含了某个进程所在的cgroup信息。下面，我们查看一下当前bash进程所在的cgroup信息，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ cat &amp;#x2F;proc&amp;#x2F;3894&amp;#x2F;cgroup
13:name&amp;#x3D;test:&amp;#x2F;test2&amp;#x2F;test22
12:freezer:&amp;#x2F;
11:perf_event:&amp;#x2F;
10:blkio:&amp;#x2F;user.slice
9:devices:&amp;#x2F;user.slice
8:hugetlb:&amp;#x2F;
7:cpu,cpuacct:&amp;#x2F;user.slice
6:net_cls,net_prio:&amp;#x2F;
5:memory:&amp;#x2F;user.slice
4:rdma:&amp;#x2F;
3:pids:&amp;#x2F;user.slice&amp;#x2F;user-1001.slice&amp;#x2F;session-4.scope
2:cpuset:&amp;#x2F;
1:name&amp;#x3D;systemd:&amp;#x2F;user.slice&amp;#x2F;user-1001.slice&amp;#x2F;session-4.scope
0::&amp;#x2F;user.slice&amp;#x2F;user-1001.slice&amp;#x2F;session-4.scope&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，当前bash进程加入了多个cgroup，其中带下划线的行为我们刚刚加入的cgroup。&lt;/p&gt;
&lt;p&gt;要想将hierarchy与子系统关联起来，需要在-o选项中指定子系统名称。下面演示了如何将memory子系统与新挂载的cgroup文件系统关联起来。代码如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo mkdir cg&amp;#x2F;memory
phl@kernelnewbies:~$ sudo mount -t cgroup -o memory memcg cg&amp;#x2F;memory&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;由于很多发行版的操作系统已经为我们配置好了这些cgroup文件系统，我们应当直接使用这些已经挂在好的文件系统，不需要自己去挂载。&lt;/p&gt;
&lt;p&gt;另外，当创建子进程时，子进程会自动加入父进程所在的cgroup。&lt;/p&gt;
&lt;h3 id=&#34;限制内存&#34;&gt;限制内存&lt;/h3&gt;
&lt;h4 id=&#34;用CGroups限制内存&#34;&gt;用CGroups限制内存&lt;/h4&gt;
&lt;p&gt;下面我们将介绍演示CGroups如何限制进程使用的内存资源，我们以内存为例进行讲解。&lt;/p&gt;
&lt;p&gt;Ubuntu18.04已经为我们挂载了一个关联memory子系统的cgroup虚拟文件系统。我们用mount命令查看一下该系统挂载到了何处，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ mount | grep cgroup
tmpfs on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup type tmpfs (ro,nosuid,nodev,noexec,mode&amp;#x3D;755)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name&amp;#x3D;systemd)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;该系统挂载到了/sys/fs/cgroup/memory目录下。我们在该hierarchy中创建一个test cgroup并查看该cgroup的目录结构，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo mkdir &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;test
phl@kernelnewbies:~$ tree &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;test
&amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;test
├── cgroup.clone_children
├── cgroup.event_control
├── cgroup.procs
├── memory.failcnt
├── memory.force_empty
├── memory.kmem.failcnt
├── memory.kmem.limit_in_bytes
├── memory.kmem.max_usage_in_bytes
├── memory.kmem.slabinfo
├── memory.kmem.tcp.failcnt
├── memory.kmem.tcp.limit_in_bytes
├── memory.kmem.tcp.max_usage_in_bytes
├── memory.kmem.tcp.usage_in_bytes
├── memory.kmem.usage_in_bytes
├── memory.limit_in_bytes
├── memory.max_usage_in_bytes
├── memory.move_charge_at_immigrate
├── memory.numa_stat
├── memory.oom_control
├── memory.pressure_level
├── memory.soft_limit_in_bytes
├── memory.stat
├── memory.swappiness
├── memory.usage_in_bytes
├── memory.use_hierarchy
├── notify_on_release
└── tasks
0 directories, 27 files&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，新建的test cgroup中有许多文件，这些文件中存放着资源限制信息。其中memory.limit_in_bytes里面存放的是该cgroup中的进程能够使用的内存额度。&lt;/p&gt;
&lt;p&gt;下面，我们将当前bash加入到test cgroup中并查看当前bash所属的cgroup信息。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ echo $$
2984
phl@kernelnewbies:~$ sudo sh -c &amp;quot;echo 2984 &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;test&amp;#x2F;cgroup.procs&amp;quot;
phl@kernelnewbies:~$ cat &amp;#x2F;proc&amp;#x2F;2984&amp;#x2F;cgroup
12:devices:&amp;#x2F;user.slice
11:hugetlb:&amp;#x2F;
10:memory:&amp;#x2F;test
9:rdma:&amp;#x2F;
8:perf_event:&amp;#x2F;
7:blkio:&amp;#x2F;user.slice
6:cpu,cpuacct:&amp;#x2F;user.slice
5:pids:&amp;#x2F;user.slice&amp;#x2F;user-1001.slice&amp;#x2F;session-4.scope
4:freezer:&amp;#x2F;
3:cpuset:&amp;#x2F;
2:net_cls,net_prio:&amp;#x2F;
1:name&amp;#x3D;systemd:&amp;#x2F;user.slice&amp;#x2F;user-1001.slice&amp;#x2F;session-4.scope
0::&amp;#x2F;user.slice&amp;#x2F;user-1001.slice&amp;#x2F;session-4.scope&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，当前bash所属的memory cgroup变为了/test，该目录为一个相对于root cgroup的相对路径。&lt;/p&gt;
&lt;p&gt;然后，将100M写入test cgroup中的memory.limit_in_bytes文件中，命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ sudo sh -c &amp;quot;echo 100M &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;test&amp;#x2F;memory.limit_in_bytes&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;我们在当前bash中启动一个占用300M进程的stress进程，该stress进程是bash的子进程，其与bash进程都在test cgroup中。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ stress --vm 1 --vm-bytes 300M --vm-keep&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;启动一个新的Shell窗口，执行top命令查看stress进程占用的内存。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
14216 root      20   0  315440 101224    264 D 27.7  2.5   0:02.66 stress&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，stress进程占用了2.5%的内存。我的电脑的内存为4G，4G * 2.5% = 100M，stress进程确实受到了cgroup中设置的内存额度的限制。&lt;/p&gt;
&lt;h4 id=&#34;docker-sh-4&#34;&gt;&lt;code&gt;docker.sh&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;下有了以上关于CGroups的知识，我们就可以将限制内存的功能加入到docker.sh中了。限制内存的功能将放在container.sh中，带下划线的行是我们为实现限制内存而新添的代码。修改后的container.sh脚本如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;hostname $container
mkdir -p &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container
echo $$ &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container&amp;#x2F;cgroup.procs
echo $memory &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container&amp;#x2F;memory.limit_in_bytes
mount -t proc proc &amp;#x2F;proc
exec $program&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;首先，我们根据容器的名字创建cgroup，命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir -p &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，我们将当前bash加入到我们创建的cgroup中，命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;echo $$ &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container&amp;#x2F;cgroup.procs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;最后，我们将内存限制写入新cgroup的memory.limit_in_bytes文件中，命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;echo $memory &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container&amp;#x2F;memory.limit_in_bytes&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;现在，我们运行&lt;code&gt;docker.sh&lt;/code&gt;，并启动一个占用300M进程的stress进程。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo .&amp;#x2F;docker.sh -c run -m 100M -C dreamland -I ubuntu1604 -V data1 -P &amp;#x2F;bin&amp;#x2F;bash
root@dreamland:~&amp;#x2F;docker.sh# stress --vm 1 --vm-bytes 300M --vm-keep
stress: info: [12] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;启动一个新的Shell窗口，执行top命令查看stress进程占用的内存。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
14216 root      20   0  315440 101224    264 D 27.7  2.5   0:02.66 stress&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，容器内的stress进程只使用了100M的内存。&lt;/p&gt;
&lt;h2 id=&#34;切换根文件系统&#34;&gt;切换根文件系统&lt;/h2&gt;
&lt;h3 id=&#34;根文件系统&#34;&gt;根文件系统&lt;/h3&gt;
&lt;p&gt;在容器技术中，根文件系统可为容器进程提供一个与主机不一致的文件系统环境。举个例子，主机为Ubuntu 18.04，创建的容器采用Ubuntu 16.04的根文件系统，那么容器运行时所用的软件及其依赖库、配置文件等都是Ubuntu 16.04的。尽管该容器使用的内核是仍旧是Ubuntu 18.04的，但应用软件的表现却与Ubuntu 16.04一致，从虚拟化的角度来说该容器就是一个Ubuntu 16.04系统。&lt;/p&gt;
&lt;p&gt;debootstrap是Ubuntu下的一个工具，用来构建根文件系统。生成的目录符合Linux文件系统标准，即包含了/boot、/etc、/bin、/usr等目录。debootstrap的安装命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;sudo apt install debootstrap&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;下面我们通过debootstrap构建Ubuntu 16.04的根文件系统。为了清晰，我们在images目录下生成根文件系统。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ mkdir images
phl@kernelnewbies:~&amp;#x2F;docker.sh$ cd images
phl@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images$ sudo debootstrap --arch amd64 xenial .&amp;#x2F;ubuntu1604&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;制作根文件系统需要从服务器下载很多文件，很耗时，请耐心等待。当文件系统制作好后，可以使用tree命令查看生成的根文件系统。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images$ tree -L 1 ubuntu1604&amp;#x2F;
ubuntu1604&amp;#x2F;
├── bin
├── boot
├── dev
├── etc
├── home
├── lib
├── lib64
├── media
├── mnt
├── old_root
├── opt
├── proc
├── root
├── run
├── sbin
├── srv
├── sys
├── tmp
├── usr
└── var
20 directories, 0 files&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;这个根文件系统与Linux系统目录很相近，我们后续的实验将使用该根文件系统。&lt;/p&gt;
&lt;h3 id=&#34;pivot-root&#34;&gt;pivot_root&lt;/h3&gt;
&lt;p&gt;pivot_root命令用于切换根文件系统，其使用方式如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;pivot_root new_root put_old&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;pivot_root将当前进程的根文件系统移至put_old目录并使new_root目录成为新的根文件系统。&lt;/p&gt;
&lt;p&gt;下面我们将通过实验学习pivot_root的使用方法。为了简单，我们在一个新的mount namespace下进行实验。首先，我们创建一个新的mount namespace，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images$ sudo unshare --mount &amp;#x2F;bin&amp;#x2F;bash
root@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images#&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;在我们的实验中，我们的根文件系统将挂载在ubuntu1604目录，而老的根文件系统将被移动到ubuntu1604/old_root目录下。我们先创建old_root目录，命令如下：&lt;/p&gt;
&lt;p&gt;root@kernelnewbies:~/docker.sh/images# mkdir -p ubuntu1604/old_root/&lt;/p&gt;
&lt;p&gt;由于pivot_root命令要求老的根目录和新的根目录不能在同一个挂载点下，因此我们通过bind mount将ubuntu1604目录变成一个挂载点。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images# mount --bind ubuntu1604 ubuntu1604
root@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images# cat &amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mountinfo | grep ubuntu1604
624 382 8:1 &amp;#x2F;home&amp;#x2F;phl&amp;#x2F;docker.sh&amp;#x2F;images&amp;#x2F;ubuntu1604 &amp;#x2F;home&amp;#x2F;phl&amp;#x2F;docker.sh&amp;#x2F;images&amp;#x2F;ubuntu1604 rw,relatime - ext4 &amp;#x2F;dev&amp;#x2F;sda1 rw,errors&amp;#x3D;remount-ro&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;准备好切换根文件系统所需要的条件后，我们调用pivot_root切换根文件系统。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images# cd ubuntu1604&amp;#x2F;
root@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images&amp;#x2F;ubuntu1604# pivot_root . old_root&amp;#x2F;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;此时，已完成根文件系统的切换，/proc文件系统也被挪到了&lt;br&gt;
/home/phl/docker.sh/images/ubuntu1604/old_root/proc，也就是说当前没有/proc文件系统，因此，我们无法查看挂载点信息，自然也无法执行一些依赖于/proc文件系统的操作。我们需要重新挂载/proc文件系统。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images&amp;#x2F;ubuntu1604# mount -t proc proc &amp;#x2F;proc&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;重新挂载/proc文件系统后，我们就可以查看当前的挂载点信息了。通过读取/proc/self/mountinfo文件来查看系统的挂载点信息。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images&amp;#x2F;ubuntu1604# cat &amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mountinfo
382 624 8:1 &amp;#x2F; &amp;#x2F;old_root rw,relatime - ext4 &amp;#x2F;dev&amp;#x2F;sda1 rw,errors&amp;#x3D;remount-ro
...
624 381 8:1 &amp;#x2F;home&amp;#x2F;phl&amp;#x2F;docker.sh&amp;#x2F;images&amp;#x2F;ubuntu1604 &amp;#x2F; rw,relatime - ext4 &amp;#x2F;dev&amp;#x2F;sda1 rw,errors&amp;#x3D;remount-ro
625 624 0:5 &amp;#x2F; &amp;#x2F;proc rw,relatime - proc proc rw&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;此时的挂载点很多，为了方便查看，此处只保留了一些主要的挂载点信息。这些挂载点信息包括/、/proc、/old_root。/old_root为老的根文件系统，我们需要将其卸载。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images&amp;#x2F;ubuntu1604# umount -l &amp;#x2F;old_root&amp;#x2F;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;卸载掉老的根文件系统后，我们再查看系统的挂载点信息。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@kernelnewbies:~&amp;#x2F;docker.sh&amp;#x2F;images&amp;#x2F;ubuntu1604# cat &amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mountinfo
624 381 8:1 &amp;#x2F;home&amp;#x2F;phl&amp;#x2F;docker.sh&amp;#x2F;images&amp;#x2F;ubuntu1604 &amp;#x2F; rw,relatime - ext4 &amp;#x2F;dev&amp;#x2F;sda1 rw,errors&amp;#x3D;remount-ro
625 624 0:5 &amp;#x2F; &amp;#x2F;proc rw,relatime - proc proc rw&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;此时，挂载点信息中只有/、/proc，不再有主机的挂载点信息。&lt;/p&gt;
&lt;h3 id=&#34;docker-sh-5&#34;&gt;&lt;code&gt;docker.sh&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;有了以上关于切换根文件系统的知识，我们就可以将切换根文件系统的功能加入到&lt;code&gt;docker.sh&lt;/code&gt;中了。切换根文件系统的功能将放在&lt;code&gt;container.sh&lt;/code&gt;中，带下划线的行是我们为实现切换根文件系统而新添的代码。修改后的&lt;code&gt;container.sh&lt;/code&gt;脚本如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;#!&amp;#x2F;bin&amp;#x2F;bash

hostname $container

mkdir -p &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container
echo $$ &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container&amp;#x2F;cgroup.procs
echo $memory &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container&amp;#x2F;memory.limit_in_bytes

mkdir -p images&amp;#x2F;$image&amp;#x2F;old_root
mount --bind images&amp;#x2F;$image images&amp;#x2F;$image

cd images&amp;#x2F;$image
pivot_root . .&amp;#x2F;old_root

mount -t proc proc &amp;#x2F;proc
umount -l &amp;#x2F;old_root

exec $program&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;首先，我们在新的根文件系统目录中创建挂载老的根文件系统的目录。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir -p images&amp;#x2F;$image&amp;#x2F;old_root&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，我们将新根文件系统目录bind mount成一个挂载点。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mount --bind images&amp;#x2F;$image images&amp;#x2F;$image&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，我们切换根文件系统。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;cd images&amp;#x2F;$image
pivot_root . .&amp;#x2F;old_root&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;最后，我们重新挂载/proc文件系统，然后卸载掉老的根文件系统。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mount -t proc proc &amp;#x2F;proc
umount -l &amp;#x2F;old_root&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;现在，我们运行&lt;code&gt;docker.sh&lt;/code&gt;，并查看当前的发行版信息。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo .&amp;#x2F;docker.sh -c run -m 100M -C dreamland -I ubuntu1604 -V data1 -P &amp;#x2F;bin&amp;#x2F;bash
root@dreamland:&amp;#x2F;# cat &amp;#x2F;etc&amp;#x2F;issue
Ubuntu 16.04 LTS \n \l&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看出，读出的发行版信息是Ubuntu 16.04 LTS \n \l，而非主机的Ubuntu 18.04.3 LTS \n \l。这说明当前使用的根文件系统确实是ubuntu16.04目录下的根文件系统，而非主机的根文件系统。&lt;/p&gt;
&lt;p&gt;我们再查看一下当前的挂载点信息，看看是否只有/与/proc。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;root@dreamland:&amp;#x2F;# cat &amp;#x2F;proc&amp;#x2F;self&amp;#x2F;mountinfo
625 381 8:1 &amp;#x2F;home&amp;#x2F;phl&amp;#x2F;docker.sh&amp;#x2F;images&amp;#x2F;ubuntu1604 &amp;#x2F; rw,relatime - ext4 &amp;#x2F;dev&amp;#x2F;sda1 rw,errors&amp;#x3D;remount-ro
626 625 0:52 &amp;#x2F; &amp;#x2F;proc rw,relatime - proc proc rw&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可看出，当前挂载点信息中只有/、/proc，不再有主机的挂载点信息。&lt;/p&gt;
&lt;p&gt;通过根文件系统，我们实现了在容器中虚拟出与主机不一样的操作系统的功能。&lt;/p&gt;
&lt;h2 id=&#34;联合加载&#34;&gt;联合加载&lt;/h2&gt;
&lt;h3 id=&#34;联合加载简介&#34;&gt;联合加载简介&lt;/h3&gt;
&lt;p&gt;联合加载指的是一次同时加载多个文件系统，但是在外面看起来只能看到 一个文件系统。联合加载会将各层文件系统叠加到一起，这样最终的文件系统会 包含所有底层的文件和目录。&lt;/p&gt;
&lt;p&gt;联合加载的多个文件系统中有一个是可读写文件系统，称为读写层，其他文件系统是只读的，称为只读层。当联合加载的文件系统发生变化时，这些变化都应用到这个读写层。比如，如果想修改一个文件，这个文件首先会从只读层复制到读写层。原只读层中的文件依然存在，但是被读写层中的该文件副本所隐藏。我们以后读写该文件时，都是读写的该文件在读写层中的副本。这种机制被称为 写时复制。&lt;/p&gt;
&lt;p&gt;我们之前实现的&lt;code&gt;docker.sh&lt;/code&gt;，有一个很大的缺陷。那就是，如果使用相同的根文件系统同时启动多个容器的实例，那么，这些容器实例使用的根文件系统位于同一个目录。我们在不同的容器实例对根文件系统所作的修改，这些容器彼此之间都可以看到，甚至一个容器可以覆覆盖另一个容器所作的修改。同时，容器实例退出时，对根文件系统所作的修改也直接作用于其所使用的根文件系统。当我们使用该根文件系统再次启动容器实例时，新启动的容器实例也可以看到以前的这些修改。例如，我们用ubuntu1604根文件系统启动两个容器实例，命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo .&amp;#x2F;docker.sh -c run -m 100M -C dreamland -I ubuntu1604 -V data1 -P &amp;#x2F;bin&amp;#x2F;bash
phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo .&amp;#x2F;docker.sh -c run -m 100M -C dreamland2 -I ubuntu1604 -V data1 -P &amp;#x2F;bin&amp;#x2F;bash&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;这两个容器实例对根文件系统做的修改彼此都可以看到。容器实例退出时，这些修改也被保存了下来，当用ubuntu1604根文件系统启动新的容器实例时，新实例也可看到以前实例所做的修改。&lt;/p&gt;
&lt;p&gt;如果容器使用的根文件系统是一个联合加载的文件系统，原先的根文件系统作为一个只读层，再添加一个读写层，那么，在容器内所作的修改都将只作用于读写层。为了区分，我们以后称ubuntu1604目录下的根文件系统为镜像。而我们可以为每一个容器实例指定一个唯一的读写层目录，这样的话，多个容器实例就可以使用同一个镜像，容器内所作的修改不会影响彼此，也不会影响到以后启动的容器实例。例如：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo .&amp;#x2F;docker.sh -c run -m 100M -C dreamland -I ubuntu1604 -V data1 -P &amp;#x2F;bin&amp;#x2F;bash
phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo .&amp;#x2F;docker.sh -c run -m 100M -C dreamland2 -I ubuntu1604 -V data1 -P &amp;#x2F;bin&amp;#x2F;bash&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;我们使用ubuntu1604镜像启动了两个容器示例，并在容器实例里进行读写操作。这两个容器实例的读写层目录是不一样的，在容器实例中所作的修改只作用于各自的读写层，彼此之间不会影响，当然更不会影响到后续启动的容器实例。&lt;/p&gt;
&lt;h3 id=&#34;AUFS&#34;&gt;AUFS&lt;/h3&gt;
&lt;p&gt;AUFS是一个实现了联合加载功能的文件系统。我们将采用AUFS实现docker.sh中的联合加载功能。&lt;/p&gt;
&lt;p&gt;下面，我们将通过实验演示一下AUFS文件系统的用法。首先，我们准备需要用到的目录及文件。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~$ mkdir aufs
phl@kernelnewbies:~$ cd aufs&amp;#x2F;
phl@kernelnewbies:~&amp;#x2F;aufs$ mkdir rw r1 r2 union
phl@kernelnewbies:~&amp;#x2F;aufs$ echo hello r1 &amp;gt; r1&amp;#x2F;hellor1.txt
phl@kernelnewbies:~&amp;#x2F;aufs$ echo hello r2 &amp;gt; r2&amp;#x2F;hellor2.txt
phl@kernelnewbies:~&amp;#x2F;aufs$ echo hello rw &amp;gt; rw&amp;#x2F;hellorw.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;下表列出了各个目录的作用。列表如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;rw为aufs文件系统的读写层目录&lt;/li&gt;
&lt;li&gt;r1为aufs文件系统的只读层目录&lt;/li&gt;
&lt;li&gt;r2为aufs文件系统的只读层目录&lt;/li&gt;
&lt;li&gt;union为挂载点，联合加载的aufs文件系统挂载于此目录&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面我们将rw、r1、r2联合加载到union目录。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;aufs$ sudo mount -t aufs -o dirs&amp;#x3D;rw:r1:r2 none union&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;-t aufs表示要挂载的文件系统类型为AUFS&lt;/li&gt;
&lt;li&gt;-o dirs=rw:r1:r2表示要将哪些目录加载到afus文件系统中，多个目录之间以:分隔。目录列表中的第一个目录表示读写层目录&lt;/li&gt;
&lt;li&gt;union表示aufs文件系统要挂载的目录&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;挂载好AUFS文件系统后，我们进入该文件系统，查看其内容。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;aufs$ cd union&amp;#x2F;
phl@kernelnewbies:~&amp;#x2F;aufs&amp;#x2F;union$ ls
hellor1.txt hellor2.txt hellorw.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从输出结果来看，rw、r1、r2目录下的内容全部出现在了AUFS文件系统中，该文件系统由rw、r1、r2目录叠加而成。&lt;/p&gt;
&lt;p&gt;然后，我们修改这些文件，看看原始的rw、r1、r2目录下的文件是否更改。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;aufs&amp;#x2F;union$ echo hello to r1 from union &amp;gt; hellor1.txt
phl@kernelnewbies:~&amp;#x2F;aufs&amp;#x2F;union$ echo hello to r2 from union &amp;gt; hellor2.txt
phl@kernelnewbies:~&amp;#x2F;aufs&amp;#x2F;union$ echo hello to rw from union &amp;gt; hellorw.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;我们返回到aufs目录，直接查看aufs目录下的内容。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;aufs$ tree .
.
├── r1
│   └── hellor1.txt
├── r2
│   └── hellor2.txt
├── rw
│   ├── hellor1.txt
│   ├── hellor2.txt
│   └── hellorw.txt
└── union
    ├── hellor1.txt
    ├── hellor2.txt
    └── hellorw.txt

4 directories, 8 files&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从输出结果我们可以看到，我们修改的hellor1.txt和hellor2.txt文件分别被拷贝了一份放在读写层目录rw中。我们查看一下这些文件的内容，命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;aufs$ cat r1&amp;#x2F;hellor1.txt
hello r1
phl@kernelnewbies:~&amp;#x2F;aufs$ cat r2&amp;#x2F;hellor2.txt
hello r2
phl@kernelnewbies:~&amp;#x2F;aufs$ cat rw&amp;#x2F;hellor1.txt
hello to r1 from union
phl@kernelnewbies:~&amp;#x2F;aufs$ cat rw&amp;#x2F;hellor2.txt
hello to r2 from union
phl@kernelnewbies:~&amp;#x2F;aufs$ cat rw&amp;#x2F;hellorw.txt
hello to rw from union&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从输出结果我们看到，用户修改只读层r1、r2中的文件时，这些文件被复制到了读写层，我们修改的是读写层的副本，原只读层中的文件没有变化。用户修改读写层rw中的文件时，修改直接作用于这些文件本身。&lt;/p&gt;
&lt;h3 id=&#34;docker-sh-6&#34;&gt;&lt;code&gt;docker.sh&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;在继续之前，我们需要将上一章在ubuntu1604根文件系统中创建的old_root目录删除掉，以保证该根文件系统跟刚制作好时一样。命令及结果如下：&lt;/p&gt;
&lt;p&gt;phl@kernelnewbies:~/docker.sh$ sudo rm -rf images/ubuntu1604/old_root&lt;/p&gt;
&lt;p&gt;有了以上关于联合加载的介绍，我们就可以将联合加载功能加入到&lt;code&gt;docker.sh&lt;/code&gt;中了。联合加载功能将放在&lt;code&gt;container.sh&lt;/code&gt;脚本中，带下划线的行是我们为实现联合加载功能而新添的代码。修改后的&lt;code&gt;container.sh&lt;/code&gt;如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;#!&amp;#x2F;bin&amp;#x2F;bash

hostname $container

mkdir -p &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container
echo $$ &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container&amp;#x2F;cgroup.procs
echo $memory &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container&amp;#x2F;memory.limit_in_bytes

mkdir -p $container&amp;#x2F;rwlayer
mount -t aufs -o dirs&amp;#x3D;$container&amp;#x2F;rwlayer:.&amp;#x2F;images&amp;#x2F;$image none $container

mkdir -p $container&amp;#x2F;old_root
cd $container
pivot_root . .&amp;#x2F;old_root

mount -t proc proc &amp;#x2F;proc
umount -l &amp;#x2F;old_root

exec $program&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;首先，我们根据容器的名字创建联合加载需要的读写层目录及文件系统挂载目录。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir -p $container&amp;#x2F;rwlayer&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;假如我们传递的容器的名字为dreamland，将创建以下目录：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ tree dreamland&amp;#x2F;
dreamland&amp;#x2F;
└── rwlayer&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;其中dreamland/rwlayer目录为创建的AUFS文件系统的读写层，dreamland目录为AUFS文件系统的挂载点。&lt;/p&gt;
&lt;p&gt;然后我们将镜像目录、读写层目录联合加载到挂载点目录。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mount -t aufs -o dirs&amp;#x3D;$container&amp;#x2F;rwlayer:.&amp;#x2F;images&amp;#x2F;$image none $container&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;假如容器名字为dreamland，使用的镜像为ubuntu1604根文件系统，dreamland/rwlayer、images/ubuntu1604将被联合加载的dreamland目录。其中，dreamland/rwlayer为AUFS文件系统的读写层，images/ubuntu1604为AUFS文件系统的只读层。&lt;/p&gt;
&lt;p&gt;之前我们将老的根文件系统挪到了rootfs/old_root，rootfs代表一个具体的镜像目录。创建old_root目录时直接修改了该镜像。下面我们将老的根文件系统的挂载点目录放在AUFS文件系统中，并将老的根文件系统挪到此处。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir -p $container&amp;#x2F;old_root
cd $container
pivot_root . .&amp;#x2F;old_root&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;此时，$container目录本身就是一个挂载点，挂载了AUFS文件系统。因此下面的代码就被移除了：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mount --bind images&amp;#x2F;$image images&amp;#x2F;$image&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;现在，我们运行&lt;code&gt;docker.sh&lt;/code&gt;，并在/root下创建一个文件。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo .&amp;#x2F;docker.sh -c run -m 100M -C dreamland -I ubuntu1604 -V data1 -P &amp;#x2F;bin&amp;#x2F;bash
root@dreamland:&amp;#x2F;# cd &amp;#x2F;root
root@dreamland:&amp;#x2F;root# ls
root@dreamland:&amp;#x2F;root# cat &amp;#x2F;etc&amp;#x2F;issue &amp;gt; hello.txt
root@dreamland:&amp;#x2F;root# cat hello.txt
Ubuntu 16.04 LTS \n \l&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;启动一个新的Shell窗口，查看一下该容器使用的AUFS文件系统。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo tree dreamland&amp;#x2F;
dreamland&amp;#x2F;
└── rwlayer
    ├── old_root
    └── root
        └── hello.txt

2 directories, 1 file&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，我们新建的文件及创建的老根文件系统的挂载点目录都出现在了读写层。我们再查看一下新创建的文件。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo cat dreamland&amp;#x2F;rwlayer&amp;#x2F;root&amp;#x2F;hello.txt
Ubuntu 16.04 LTS \n \l&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;文件内容是Ubuntu 16.04的发行版信息。&lt;/p&gt;
&lt;p&gt;通过联合加载，我们实现了在容器中的读写不会影响使用的镜像。这样使用ubuntu1604镜像创建多个容器时，彼此之间就不会相互影响了。&lt;/p&gt;
&lt;h2 id=&#34;卷&#34;&gt;卷&lt;/h2&gt;
&lt;h3 id=&#34;卷简介&#34;&gt;卷简介&lt;/h3&gt;
&lt;p&gt;卷是容器内的一个目录，这个目录可以绕过联合文件系统，提供数据共享（容器所使用的的联合文件系统不应该被主机或其他容器访问）与数据持久化的功能。&lt;/p&gt;
&lt;p&gt;举个例子，假如容器有个目录为/data的卷，我们向这个卷写入的内容不会出现在联合文件系统的读写层，而是直接出现在这个目录里。主机与其他容器也可以访问该目录，从而达到数据共享与数据持久化的目的。&lt;/p&gt;
&lt;p&gt;卷位于联合文件系统中，通常来说写入该目录的内容会被写入容器的读写层中，那么怎样才能是写入卷的目录直接出现在该目录中，而不是容器读写层呢？其实方法很简单，只要我们将该目录变成一个挂载点就行，变成挂载点后，这个目录中的内容就不属于联合文件系统了，写入该目录的内容自然会保存在挂载到该挂载点的设备中。&lt;/p&gt;
&lt;h3 id=&#34;docker-sh-7&#34;&gt;&lt;code&gt;docker.sh&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;有了以上关于卷的介绍，我们就可以将卷功能加入到docker.sh中了。卷功能将放在container.sh脚本中，带下划线的行是我们为实现卷功能而新添的代码。修改后的container.sh脚本如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;#!&amp;#x2F;bin&amp;#x2F;bash

hostname $container

mkdir -p &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container
echo $$ &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container&amp;#x2F;cgroup.procs
echo $memory &amp;gt; &amp;#x2F;sys&amp;#x2F;fs&amp;#x2F;cgroup&amp;#x2F;memory&amp;#x2F;$container&amp;#x2F;memory.limit_in_bytes

mkdir -p $container&amp;#x2F;rwlayer
mount -t aufs -o dirs&amp;#x3D;$container&amp;#x2F;rwlayer:.&amp;#x2F;images&amp;#x2F;$image none $container

mkdir -p $volume
mkdir -p $container&amp;#x2F;$volume
mount --bind $volume $container&amp;#x2F;$volume

mkdir -p $container&amp;#x2F;old_root
cd $container
pivot_root . .&amp;#x2F;old_root

mount -t proc proc &amp;#x2F;proc
umount -l &amp;#x2F;old_root

exec $program&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;首先，我们根据卷的名字创建主机卷目录，我们在容器内部对卷的修改，都将作用于此目录。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir -p $volume&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，我们在容器内部创建同名卷目录，该目录本身会出现在容器的读写层中，因为该目录是在AUFS文件系统中创建的。因为
    &lt;span id=&#34;mjx-58bc76c8&#34;&gt;
      &lt;style&gt;
      #mjx-58bc76c8{
        display:contents;
        mjx-assistive-mml {
          user-select: text !important;
          clip: auto !important;
          color: rgba(0,0,0,0);
        }
        
mjx-container[jax=&#34;SVG&#34;] {
  direction: ltr;
}

mjx-container[jax=&#34;SVG&#34;] &gt; svg {
  overflow: visible;
  min-height: 1px;
  min-width: 1px;
}

mjx-container[jax=&#34;SVG&#34;] &gt; svg a {
  fill: blue;
  stroke: blue;
}

mjx-assistive-mml {
  position: absolute !important;
  top: 0px;
  left: 0px;
  clip: rect(1px, 1px, 1px, 1px);
  padding: 1px 0px 0px 0px !important;
  border: 0px !important;
  display: block !important;
  width: auto !important;
  overflow: hidden !important;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

mjx-assistive-mml[display=&#34;block&#34;] {
  width: 100% !important;
}

mjx-container[jax=&#34;SVG&#34;][display=&#34;true&#34;] {
  display: block;
  text-align: center;
  margin: 1em 0;
}

mjx-container[jax=&#34;SVG&#34;][display=&#34;true&#34;][width=&#34;full&#34;] {
  display: flex;
}

mjx-container[jax=&#34;SVG&#34;][justify=&#34;left&#34;] {
  text-align: left;
}

mjx-container[jax=&#34;SVG&#34;][justify=&#34;right&#34;] {
  text-align: right;
}

g[data-mml-node=&#34;merror&#34;] &gt; g {
  fill: red;
  stroke: red;
}

g[data-mml-node=&#34;merror&#34;] &gt; rect[data-background] {
  fill: yellow;
  stroke: none;
}

g[data-mml-node=&#34;mtable&#34;] &gt; line[data-line], svg[data-table] &gt; g &gt; line[data-line] {
  stroke-width: 70px;
  fill: none;
}

g[data-mml-node=&#34;mtable&#34;] &gt; rect[data-frame], svg[data-table] &gt; g &gt; rect[data-frame] {
  stroke-width: 70px;
  fill: none;
}

g[data-mml-node=&#34;mtable&#34;] &gt; .mjx-dashed, svg[data-table] &gt; g &gt; .mjx-dashed {
  stroke-dasharray: 140;
}

g[data-mml-node=&#34;mtable&#34;] &gt; .mjx-dotted, svg[data-table] &gt; g &gt; .mjx-dotted {
  stroke-linecap: round;
  stroke-dasharray: 0,140;
}

g[data-mml-node=&#34;mtable&#34;] &gt; g &gt; svg {
  overflow: visible;
}

[jax=&#34;SVG&#34;] mjx-tool {
  display: inline-block;
  position: relative;
  width: 0;
  height: 0;
}

[jax=&#34;SVG&#34;] mjx-tool &gt; mjx-tip {
  position: absolute;
  top: 0;
  left: 0;
}

mjx-tool &gt; mjx-tip {
  display: inline-block;
  padding: .2em;
  border: 1px solid #888;
  font-size: 70%;
  background-color: #F8F8F8;
  color: black;
  box-shadow: 2px 2px 5px #AAAAAA;
}

g[data-mml-node=&#34;maction&#34;][data-toggle] {
  cursor: pointer;
}

mjx-status {
  display: block;
  position: fixed;
  left: 1em;
  bottom: 1em;
  min-width: 25%;
  padding: .2em .4em;
  border: 1px solid #888;
  font-size: 90%;
  background-color: #F8F8F8;
  color: black;
}

foreignObject[data-mjx-xml] {
  font-family: initial;
  line-height: normal;
  overflow: visible;
}

mjx-container[jax=&#34;SVG&#34;] path[data-c], mjx-container[jax=&#34;SVG&#34;] use[data-c] {
  stroke-width: 3;
}

g[data-mml-node=&#34;xypic&#34;] path {
  stroke-width: inherit;
}

.MathJax g[data-mml-node=&#34;xypic&#34;] path {
  stroke-width: inherit;
}

      }
      &lt;/style&gt;
      &lt;mjx-container class=&#34;MathJax&#34; jax=&#34;SVG&#34; style=&#34;position: relative;&#34;&gt;&lt;svg style=&#34;vertical-align: -0.566ex;&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;62.828ex&#34; height=&#34;2.262ex&#34; role=&#34;img&#34; focusable=&#34;false&#34; viewBox=&#34;0 -750 27770 1000&#34; aria-hidden=&#34;true&#34;&gt;&lt;g stroke=&#34;currentColor&#34; fill=&#34;currentColor&#34; stroke-width=&#34;0&#34; transform=&#34;scale(1,-1)&#34;&gt;&lt;g data-mml-node=&#34;math&#34;&gt;&lt;g data-mml-node=&#34;mi&#34;&gt;&lt;path data-c=&#34;1D450&#34; d=&#34;M34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159Z&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(433,0)&#34;&gt;&lt;path data-c=&#34;1D45C&#34; d=&#34;M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(918,0)&#34;&gt;&lt;path data-c=&#34;1D45B&#34; d=&#34;M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(1518,0)&#34;&gt;&lt;path data-c=&#34;1D461&#34; d=&#34;M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(1879,0)&#34;&gt;&lt;path data-c=&#34;1D44E&#34; d=&#34;M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(2408,0)&#34;&gt;&lt;path data-c=&#34;1D456&#34; d=&#34;M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(2753,0)&#34;&gt;&lt;path data-c=&#34;1D45B&#34; d=&#34;M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(3353,0)&#34;&gt;&lt;path data-c=&#34;1D452&#34; d=&#34;M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(3819,0)&#34;&gt;&lt;path data-c=&#34;1D45F&#34; d=&#34;M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(4270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;目&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(5270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;录&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(6270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;为&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(7270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;容&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(8270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;器&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(9270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;的&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(10270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;根&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(11270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;目&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(12270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;录&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(13270,0)&#34;&gt;&lt;text data-variant=&#34;italic&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34; font-style=&#34;italic&#34;&gt;，&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(14270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;所&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(15270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;以&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(16270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;容&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(17270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;器&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(18270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;内&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(19270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;部&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(20270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;卷&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(21270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;目&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(22270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;录&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(23270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;的&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(24270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;路&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(25270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;径&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;mi&#34; transform=&#34;translate(26270,0)&#34;&gt;&lt;text data-variant=&#34;normal&#34; transform=&#34;scale(1,-1)&#34; font-size=&#34;884px&#34; font-family=&#34;serif&#34;&gt;为&lt;/text&gt;&lt;/g&gt;&lt;g data-mml-node=&#34;TeXAtom&#34; data-mjx-texclass=&#34;ORD&#34; transform=&#34;translate(27270,0)&#34;&gt;&lt;g data-mml-node=&#34;mo&#34;&gt;&lt;path data-c=&#34;2F&#34; d=&#34;M423 750Q432 750 438 744T444 730Q444 725 271 248T92 -240Q85 -250 75 -250Q68 -250 62 -245T56 -231Q56 -221 230 257T407 740Q411 750 423 750Z&#34;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;mjx-assistive-mml unselectable=&#34;on&#34; display=&#34;inline&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;mi&gt;c&lt;/mi&gt;&lt;mi&gt;o&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;目&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;录&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;为&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;容&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;器&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;的&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;根&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;目&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;录&lt;/mi&gt;&lt;mi&gt;，&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;所&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;以&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;容&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;器&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;内&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;部&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;卷&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;目&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;录&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;的&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;路&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;径&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;为&lt;/mi&gt;&lt;mrow data-mjx-texclass=&#34;ORD&#34;&gt;&lt;mo&gt;/&lt;/mo&gt;&lt;/mrow&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;
    &lt;/span&gt;
  volume。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir -p $container&amp;#x2F;$volume&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;将主机上的卷目录bind mount到容器内部的卷目录上，这样容器内部对卷目录的修改，都将作用于主机卷目录。命令如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mount --bind $volume $container&amp;#x2F;$volume&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;现在，我们运行&lt;code&gt;docker.sh&lt;/code&gt;，并在卷目录（/data1）中创建一个文件。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo .&amp;#x2F;docker.sh -c run -m 100M -C dreamland -I ubuntu1604 -V data1 -P &amp;#x2F;bin&amp;#x2F;bash
root@dreamland:&amp;#x2F;# cd &amp;#x2F;data1
root@dreamland:&amp;#x2F;data1# echo &amp;quot;hello to data1 volume from ubuntu16.04&amp;quot; &amp;gt;&amp;gt; hello.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;启动一个新的Shell窗口，查看一下该容器使用的AUFS文件系统中的内容。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo tree dreamland&amp;#x2F;
dreamland&amp;#x2F;
└── rwlayer
    ├── data1
    ├── old_root
    └── root
        └── hello.txt

4 directories, 1 file&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，我们使用的卷目录被创建在了容器的读写层，但是我们在卷目录中新建的文件却没有出现在读写层中。&lt;/p&gt;
&lt;p&gt;我们再来查看一下主机卷目录的内容。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo tree data1&amp;#x2F;
data1&amp;#x2F;
└── hello.txt

0 directories, 1 file&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，在容器内部对卷目录的修改直接作用在了主机上的卷目录。我们再来查看一下主机卷目录下hello.txt中的内容。命令及结果如下：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;phl@kernelnewbies:~&amp;#x2F;docker.sh$ sudo cat data1&amp;#x2F;hello.txt
hello to data1 volume from ubuntu16.04&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;从结果我们可以看到，该文件的内容与我们在容器内部写入hello.txt的内容一致。&lt;/p&gt;
&lt;p&gt;通过卷目录，我们实现了容器之间数据共享与数据持久化的功能。&lt;/p&gt;
&lt;h2 id=&#34;后记&#34;&gt;后记&lt;/h2&gt;
&lt;p&gt;至此，我们通过一系列的实验对docker的底层技术有了一个感性的认识。我们在使用docker时，也能够对其是如何运作的有了一个大致的了解。当然，这对于掌握docker技术来说还远远不够，有很多知识我们没有涉及，例如user namespace、容器安全、其他的CGroups、虚拟网络等。&lt;/p&gt;
&lt;p&gt;编辑整理 &lt;a href=&#34;https://www.toutiao.com/i6890898988879315468/?tt_from=weixin&amp;amp;utm_campaign=client_share&amp;amp;wxshare_count=1&amp;amp;timestamp=1647576105&amp;amp;app=news_article&amp;amp;utm_source=weixin&amp;amp;utm_medium=toutiao_android&amp;amp;use_new_style=1&amp;amp;req_id=202203181201440101511900790A2CBE46&amp;amp;share_token=b2d9351e-4cb1-4a25-ae82-f70543ce2a3b&amp;amp;group_id=6890898988879315468&#34;&gt;ScratchLab&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;系列教程&#34;&gt;&lt;strong&gt;系列教程&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;全部文章RSS订阅&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;Docker系列&#34;&gt;&lt;strong&gt;Docker系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/docker/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;Docker 分类 RSS 订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/42b6a86d/&#34;&gt;Docker使用简明教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/9912bd5d/&#34;&gt;使用jeckett,sonarr,iyuu,qt,emby打造全自动追剧流程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/1802a8a7/&#34;&gt;为知笔记私有化Docker部署&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/593cc323/&#34;&gt;Earthly 一个更加强大的镜像构建工具&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/90e60aac/&#34;&gt;使用 Shell 脚本实现一个简单 Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/465d2738/&#34;&gt;如何使用Traefik V2 在Ubuntu20.04 上面来做 Dockers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/462f1e5c/&#34;&gt;通过IPV6访问Qnap NAS中Docker的服务&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;Hexo系列&#34;&gt;&lt;strong&gt;Hexo系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/hexo/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;HexoRSS分类订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[十万字图文教程]基于Hexo的matery主题搭建博客并深度优化完全一站式教程&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/40300608/&#34;&gt;Hexo Docker环境与Hexo基础配置篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/4d8a0b22/&#34;&gt;hexo博客自定义修改篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/9b056c86/&#34;&gt;hexo博客网络优化篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/5311b619/&#34;&gt;hexo博客增强部署篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/4a2050e2/&#34;&gt;hexo博客个性定制篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/84b4059a/&#34;&gt;hexo博客常见问题篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/253706ff/&#34;&gt;hexo博客博文撰写篇之完美笔记大攻略终极完全版&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/cf0f47fd/&#34;&gt;Hexo Markdown以及各种插件功能测试&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;markdown 各种其它语法插件，latex公式支持，mermaid图表，plant uml图表，URL卡片，bilibili卡片，github卡片，豆瓣卡片，插入音乐和视频，插入脑图，插入PDF，嵌入iframe&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/217ccdc1/&#34;&gt;在 Hexo 博客中插入 ECharts 动态图表&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/546887ac/&#34;&gt;使用nodeppt给hexo博客嵌入PPT演示&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/a3c81cc3/&#34;&gt;GithubProfile美化与自动获取RSS文章教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/e922fac8/&#34;&gt;Vercel部署高级用法教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/eb731135/&#34;&gt;webhook部署Hexo静态博客指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/8f9792ab/&#34;&gt;在宝塔VPS上面采用docker部署waline全流程图解教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/843eb2k9/&#34;&gt;自建Umami访问统计服务并统计静态博客UV/PV&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;笔记系列&#34;&gt;&lt;strong&gt;笔记系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/note/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;Note分类RSS订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/a8535f26/&#34;&gt;完美笔记进化论&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/253706ff/&#34;&gt;hexo博客博文撰写篇之完美笔记大攻略终极完全版&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/e6086437/&#34;&gt;Joplin入门指南&amp;amp;实践方案&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/45f878cd/&#34;&gt;替代Evernote免费开源笔记Joplin-网盘同步笔记历史版本Markdown可视化&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/92d347d6/&#34;&gt;Joplin 插件以及其Markdown语法。All in One!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/e3ee7f8b/&#34;&gt;Joplin 插件使用推荐&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/1802a8a7/&#34;&gt;为知笔记私有化Docker部署&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;Gitbook使用系列&#34;&gt;&lt;strong&gt;Gitbook使用系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/gitbook/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;Gitbook分类RSS订阅&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/7fe86002/&#34;&gt;GitBook+GitLab撰写发布技术文档-Part1:GitBook篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/7790e989/&#34;&gt;GitBook+GitLab撰写发布技术文档-Part2:GitLab篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/d6bad1e5/&#34;&gt;自己动手制作电子书的最佳方式（支持PDF、ePub、mobi等格式）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;Gitlab-使用系列&#34;&gt;&lt;strong&gt;Gitlab 使用系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/gitlab/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;Gitlab RSS 分类订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/acc13b70/&#34;&gt;&lt;strong&gt;Gitlab的安装及使用教程完全版&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/29a820b3/&#34;&gt;破解Gitlab EE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/d08eb7b/&#34;&gt;Gitlab的安装及使用&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/1879721e/&#34;&gt;CI/CD与Git Flow与GitLab&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;link rel=&#34;stylesheet&#34; href=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10/dist/style.css&#34;&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/d3@7&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-view@0.18.10&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10&#34;&gt;&lt;/script&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;/css/markmap.css&#34;&gt;

&lt;script src=&#34;/js/markmap.js&#34;&gt;&lt;/script&gt;
</content>
        <category term="linux" />
        <category term="docker" />
        <category term="shell" />
        <updated>2022-03-18T06:33:17.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.17lai.site/posts/593cc323/</id>
        <title>Earthly 一个更加强大的镜像构建工具</title>
        <link rel="alternate" href="https://blog.17lai.site/posts/593cc323/"/>
        <content type="html">&lt;h2 id=&#34;一、Earthly-介绍&#34;&gt;一、Earthly 介绍&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;开局一张图，功能全靠吹。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101210057.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;p&gt;Earthly 是一个更加高级的 Docker 镜像构建工具，Earthly 通过自己定义的 Earthfile 来代替传统的 Dockerfile 完成镜像构建；Earthfile 就如同 Earthly 官方所描述:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-none&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;**Makefile + Dockerfile &amp;#x3D; Earthfile**&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;在使用 Earthly 进行构建镜像时目前强依赖于 buildkit，Earthly 通过 buildkit 支持了一些 Dockerfile 的扩展语法，同时将 Dockerfile 与 Makefile 整合，使得多平台构建和代码化 Dockerfile 变得更加简单；使用 Earthly 可以更加方便的完成 Dockerfile 的代码复用以及更加友好的 CI 自动集成。&lt;/p&gt;
&lt;h2 id=&#34;二、快速开始&#34;&gt;二、快速开始&lt;/h2&gt;
&lt;h3 id=&#34;2-1、安装依赖&#34;&gt;2.1、安装依赖&lt;/h3&gt;
&lt;p&gt;Earthly 目前依赖于 Docker 和 Git，所以安装 Earthly 前请确保机器已经安装了 Docker 和 Git。&lt;/p&gt;
&lt;h3 id=&#34;2-2、安装-Earthly&#34;&gt;2.2、安装 Earthly&lt;/h3&gt;
&lt;p&gt;Earthly 采用 Go 编写，所以主要就一个二进制文件，Linux 下安装可以直接参考官方的安装脚本:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;sudo &amp;#x2F;bin&amp;#x2F;sh -c &amp;#39;wget https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;earthly&amp;#x2F;earthly&amp;#x2F;releases&amp;#x2F;latest&amp;#x2F;download&amp;#x2F;earthly-linux-amd64 -O &amp;#x2F;usr&amp;#x2F;local&amp;#x2F;bin&amp;#x2F;earthly &amp;amp;&amp;amp; chmod +x &amp;#x2F;usr&amp;#x2F;local&amp;#x2F;bin&amp;#x2F;earthly &amp;amp;&amp;amp; &amp;#x2F;usr&amp;#x2F;local&amp;#x2F;bin&amp;#x2F;earthly bootstrap --with-autocomplete&amp;#39;Copy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;安装完成后 Earthly 将会启动一个 buildkitd 容器: &lt;code&gt;earthly-buildkitd&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;2-3、语法高亮&#34;&gt;2.3、语法高亮&lt;/h3&gt;
&lt;p&gt;目前 Earthly 官方支持 VS Code、VIM 以及 Sublime Text 三种编辑器的语法高亮，具体如何安装请参考 &lt;a href=&#34;https://earthly.dev/get-earthly&#34;&gt;官方文档&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&#34;2-4、基本使用&#34;&gt;2.4、基本使用&lt;/h3&gt;
&lt;p&gt;本示例源于官方 Basic 教程，以下示例以编译 Go 项目为样例:&lt;/p&gt;
&lt;p&gt;首先创建一个任意名称的目录，目录中存在项目源码文件以及一个 &lt;code&gt;Earthfile&lt;/code&gt; 文件；&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;main.go&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-go&#34; data-language=&#34;go&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package main

import &amp;quot;fmt&amp;quot;

func main() &amp;#123;
    fmt.Println(&amp;quot;hello world&amp;quot;)
&amp;#125;Copy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Earthfile&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-docker&#34; data-language=&#34;docker&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-docker&#34;&gt;FROM golang:1.17-alpine
WORKDIR &amp;#x2F;go-example

build:
    COPY main.go .
    RUN go build -o build&amp;#x2F;go-example main.go
    SAVE ARTIFACT build&amp;#x2F;go-example &amp;#x2F;go-example AS LOCAL build&amp;#x2F;go-example

docker:
    COPY +build&amp;#x2F;go-example .
    ENTRYPOINT [&amp;quot;&amp;#x2F;go-example&amp;#x2F;go-example&amp;quot;]
    SAVE IMAGE go-example:latestCopy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;有了 &lt;code&gt;Earthfile&lt;/code&gt; 以后我们就可以使用 &lt;code&gt;Earthly&lt;/code&gt; 将其打包为镜像；&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;# 目录结构
~&amp;#x2F;t&amp;#x2F;earthlytest ❯❯❯ tree
.
├── Earthfile
└── main.go

0 directories, 2 files

# 通过 earthly 进行构建
~&amp;#x2F;t&amp;#x2F;earthlytest ❯❯❯ earthly +dockerCopy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101210338.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;p&gt;构建完成后我们就可以直接从 docker 的 images 列表中查看刚刚构建的镜像，并运行:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101212129.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;三、进阶使用&#34;&gt;三、进阶使用&lt;/h2&gt;
&lt;h3 id=&#34;3-1、多阶段构建&#34;&gt;3.1、多阶段构建&lt;/h3&gt;
&lt;p&gt;Earthfile 中包含类似 Makefile 一样的 &lt;code&gt;target&lt;/code&gt;，不同的 &lt;code&gt;target&lt;/code&gt; 之间还可以通过特定语法进行引用，每个 &lt;code&gt;target&lt;/code&gt; 都可以被单独执行，执行过程中 earthly 会自动解析这些依赖关系。&lt;/p&gt;
&lt;p&gt;这种多阶段构建时语法很弹性，我们可以在每个阶段运行独立的命令以及使用不同的基础镜像；从快速开始中可以看到，我们始终使用了一个基础镜像(&lt;code&gt;golang:1.17-alpine&lt;/code&gt;)，对于 Go 这种编译后自带运行时不依赖其语言 SDK 的应用，我们事实上可以将 “发布物” 仅放在简单的运行时系统镜像内，从而减少最终镜像体积:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101210338-2.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;p&gt;由于使用了多个 target，所以我们可以单独的运行 &lt;code&gt;build&lt;/code&gt; 这个 target 来验证我们的编译流程，&lt;strong&gt;这种多 target 的设计方便我们构建应用时对编译、打包步骤的细化拆分，同时也方便我们进行单独的验证。&lt;/strong&gt; 例如我们单独执行 &lt;code&gt;build&lt;/code&gt; 这个 target 来验证我们的编译流程是否正确:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101210338-3.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;p&gt;在其他阶段验证完成后，我们可以直接运行最终的 target，earthly 会自动识别到这种依赖关系从而自动运行其依赖的 target:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101210338-4.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;3-2、扩展指令&#34;&gt;3.2、扩展指令&lt;/h3&gt;
&lt;h4 id=&#34;3-2-1、SAVE&#34;&gt;3.2.1、SAVE&lt;/h4&gt;
&lt;p&gt;SAVE 指令是 Earthly 自己的一个扩展指令，实际上分为 &lt;code&gt;SAVE ARTIFACT&lt;/code&gt; 和 &lt;code&gt;SAVE IMAGE&lt;/code&gt;；其中 &lt;code&gt;SAVE ARTIFACT&lt;/code&gt; 指令格式如下:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-docker&#34; data-language=&#34;docker&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-docker&#34;&gt;SAVE ARTIFACT [--keep-ts] [--keep-own] [--if-exists] [--force] &amp;lt;src&amp;gt; [&amp;lt;artifact-dest-path&amp;gt;] [AS LOCAL &amp;lt;local-path&amp;gt;]Copy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;SAVE ARTIFACT&lt;/code&gt; 指令用于将文件或目录从 build 运行时环境保存到 target 的 artifact 环境；当保存到 artifact 环境后，可以通过 &lt;code&gt;COPY&lt;/code&gt; 等命令在其他位置进行引用，类似于 Dockerfile 的 &lt;code&gt;COPY --from...&lt;/code&gt; 语法；不同的是 &lt;code&gt;SAVE ARTIFACT&lt;/code&gt; 支持 &lt;code&gt;AS LOCAL &amp;lt;local-path&amp;gt;&lt;/code&gt; 附加参数，一但指定此参数后，earthly 会同时将文件或目录在宿主机复制一份，一般用于调试等目的。&lt;code&gt;SAVE ARTIFACT&lt;/code&gt; 命令在上面的样例中已经展示了，在运行完 &lt;code&gt;earthly +build&lt;/code&gt; 命令后实际上会在本地看到被 SAVE 出来的 ARTIFACT:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101210338-5.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;p&gt;而另一个 &lt;code&gt;SAVE IMAGE&lt;/code&gt; 指令则主要用于将当前的 build 环境 SAVE 为一个 IMAGE，&lt;strong&gt;如果指定了 &lt;code&gt;--push&lt;/code&gt; 选项，同时在执行 &lt;code&gt;earthly +target&lt;/code&gt; 命令时也加入 &lt;code&gt;--push&lt;/code&gt; 选项，该镜像将会自动被推送到目标 Registry 上。&lt;/strong&gt;&lt;code&gt;SAVE IMAGE&lt;/code&gt; 指令格式如下:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-docker&#34; data-language=&#34;docker&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-docker&#34;&gt;SAVE IMAGE [--cache-from&amp;#x3D;&amp;lt;cache-image&amp;gt;] [--push] &amp;lt;image-name&amp;gt;...Copy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101210338-6.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;3-2-2、GIT-CLONE&#34;&gt;3.2.2、GIT CLONE&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;GIT CLONE&lt;/code&gt; 指令用于将指定 git 仓库 clone 到 build 环境中；与 &lt;code&gt;RUN git clone...&lt;/code&gt; 命令不同的是，&lt;strong&gt;&lt;code&gt;GIT CLONE&lt;/code&gt; 通过宿主机的 git 命令运行，它不依赖于容器内的 git 命令，同时还可以直接为 earthly 配置 git 认证，从而避免将这些安全信息泄漏到 build 环境中；&lt;/strong&gt; 关于如何配置 earthly 的 git 认证请参考 &lt;a href=&#34;https://docs.earthly.dev/docs/guides/auth&#34;&gt;官方文档&lt;/a&gt;；下面是 &lt;code&gt;GIT CLONE&lt;/code&gt; 指令的样例&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101213746.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;3-2-3、COPY&#34;&gt;3.2.3、COPY&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;COPY&lt;/code&gt; 指令与标准的 Dockerfile COPY 指令类似，除了支持 Dockerfile 标准的 COPY 功能以外，&lt;strong&gt;earthly 中的 &lt;code&gt;COPY&lt;/code&gt; 指令可以引用其他 target 环节产生的 artifact，在引用时会自动声明依赖关系；即当在 &lt;code&gt;B&lt;/code&gt; target 中存在 &lt;code&gt;COPY +A/xxxxx /path/to/copy&lt;/code&gt; 类似的指令时，如果只单纯的执行 &lt;code&gt;earthly +B&lt;/code&gt;，那么 earthly 根据依赖分析会得出在 COPY 之前需要执行 target A。&lt;/strong&gt;&lt;code&gt;COPY&lt;/code&gt; 指令的语法格式如下:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-docker&#34; data-language=&#34;docker&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-docker&#34;&gt;# 与 Dockerfile 相同的使用方式，从上下文复制
COPY [options...] &amp;lt;src&amp;gt;... &amp;lt;dest&amp;gt;

# 扩展支持的从 target 复制方式
COPY [options...] &amp;lt;src-artifact&amp;gt;... &amp;lt;dest&amp;gt;Copy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h4 id=&#34;3-2-4、RUN&#34;&gt;3.2.4、RUN&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;RUN&lt;/code&gt; 指令在标准使用上与 Dockerfile 里保持一致，除此之外增加了更多的扩展选项，其指令格式如下:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-docker&#34; data-language=&#34;docker&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-docker&#34;&gt;# shell 方式运行(&amp;#x2F;bin&amp;#x2F;sh -c)
RUN [--push] [--entrypoint] [--privileged] [--secret &amp;lt;env-var&amp;gt;&amp;#x3D;&amp;lt;secret-ref&amp;gt;] [--ssh] [--mount &amp;lt;mount-spec&amp;gt;] [--] &amp;lt;command&amp;gt;

# exec 方式运行
RUN [[&amp;lt;flags&amp;gt;...], &amp;quot;&amp;lt;executable&amp;gt;&amp;quot;, &amp;quot;&amp;lt;arg1&amp;gt;&amp;quot;, &amp;quot;&amp;lt;arg2&amp;gt;&amp;quot;, ...]Copy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;其中 &lt;code&gt;--privileged&lt;/code&gt; 选项允许运行的命令使用 &lt;code&gt;privileged capabilities&lt;/code&gt;，但是需要 earthly 在运行 target 时增加 &lt;code&gt;--allow-privileged&lt;/code&gt; 选项；&lt;code&gt;--interactive / --interactive-keep&lt;/code&gt; 选项用于交互式执行一些命令，在完成交互后 build 继续进行，&lt;strong&gt;在交互过程中进行的操作都会被持久化到 镜像中:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101210338-8.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;p&gt;限于篇幅原因，其他的具体指令请查阅官方文档 &lt;a href=&#34;https://docs.earthly.dev/docs/earthfile&#34;&gt;Earthfile reference&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&#34;3-3、UDCS&#34;&gt;3.3、UDCS&lt;/h3&gt;
&lt;p&gt;UDCs 全称 “User-defined commands”，即用户定义指令；通过 UDCs 我们可以将 Earthfile 中特定的命令剥离出来，从而实现更加通用和统一的代码复用；下面是一个定义 UDCs 指令的样例:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-docker&#34; data-language=&#34;docker&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-docker&#34;&gt;# 定义一个 Command
# ⚠️ 注意: 语法必须满足以下规则
# 1、名称全大写
# 2、名称下划线分割
# 3、首个命令必须为 COMMAND(后面没有冒号)
MY_COPY:
    COMMAND
    ARG src
    ARG dest&amp;#x3D;.&amp;#x2F;
    ARG recursive&amp;#x3D;false
    RUN cp $(if $recursive &amp;#x3D;  &amp;quot;true&amp;quot;; then printf -- -r; fi) &amp;quot;$src&amp;quot; &amp;quot;$dest&amp;quot;

# target 中引用
build:
    FROM alpine:3.13
    WORKDIR &amp;#x2F;udc-example
    RUN echo &amp;quot;hello&amp;quot; &amp;gt;.&amp;#x2F;foo
    # 通过 DO 关键字引用 UDCs
    DO +MY_COPY --src&amp;#x3D;.&amp;#x2F;foo --dest&amp;#x3D;.&amp;#x2F;bar
    RUN cat .&amp;#x2F;bar # prints &amp;quot;hello&amp;quot;Copy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;UDCs 不光可以定义在一个 Earthfile 中，UDCs 可以跨文件、跨目录引用:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101212525.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;p&gt;有了 UDCs 以后，我们可以通过这种方式将对基础镜像的版本统一控制、对特殊镜像的通用处理等操作全部抽象出来，然后每个 Earthfile 根据需要进行引用；关于 UDCs 的使用样例可以参考我的 &lt;a href=&#34;https://github.com/mritd/autobuild&#34;&gt;autobuild&lt;/a&gt; 项目，其中的 &lt;a href=&#34;https://github.com/mritd/autobuild/tree/main/earthfiles/udcs&#34;&gt;udcs&lt;/a&gt; 目录定义了大量的通用 UDCs，这些 UDCs 被其他目标镜的 Earthfile 批量引用。&lt;/p&gt;
&lt;h3 id=&#34;3-4、多平台构建&#34;&gt;3.4、多平台构建&lt;/h3&gt;
&lt;p&gt;在以前使用 Dockerfile 的时候，我们需要自己配置然后开启 buildkit 来实现多平台构建；在配置过程中可能会很繁琐，现在使用 earthly 可以默认帮我们实现多平台的交叉编译，我们需要做的仅仅是在 Earthfile 中声明需要支持哪些平台而已:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101210338-10.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;p&gt;以上 Earthfile 在执行 &lt;code&gt;earthly --push +all&lt;/code&gt; 构建时，将会自动构建四个平台的镜像，并保持单个 tag，同时由于使用了 &lt;code&gt;--push&lt;/code&gt; 选项还会自动推送到 Docker Hub 上:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/11/0120211101210338-11.png&#34; alt=&#34;img&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;四、总结&#34;&gt;四、总结&lt;/h2&gt;
&lt;p&gt;Earthly 弥补了 Dockerfile 的很多不足，解决了很多痛点问题；但同样可能需要一些学习成本，但是如果已经熟悉了 Dockerfile 其实学习成本不高；所以目前还是比较推荐将 Dockerfile 切换为 Earthfile 进行统一和版本化管理的。本文由于篇幅所限(懒)很多地方没有讲，比如共享缓存等，所以关于 Earthly 更多的详细使用等最好还是仔细阅读一下&lt;a href=&#34;https://docs.earthly.dev/docs/guides&#34;&gt;官方文档&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;整理转载：&lt;a href=&#34;https://mritd.com/2021/10/27/the-best-image-build-tool-earthly/&#34;&gt;the-best-image-build-tool-earthly&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;系列教程&#34;&gt;&lt;strong&gt;系列教程&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;全部文章RSS订阅&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;Docker系列&#34;&gt;&lt;strong&gt;Docker系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/docker/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;Docker 分类 RSS 订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/42b6a86d/&#34;&gt;Docker使用简明教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/9912bd5d/&#34;&gt;使用jeckett,sonarr,iyuu,qt,emby打造全自动追剧流程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/1802a8a7/&#34;&gt;为知笔记私有化Docker部署&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/593cc323/&#34;&gt;Earthly 一个更加强大的镜像构建工具&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/90e60aac/&#34;&gt;使用 Shell 脚本实现一个简单 Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/465d2738/&#34;&gt;如何使用Traefik V2 在Ubuntu20.04 上面来做 Dockers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/462f1e5c/&#34;&gt;通过IPV6访问Qnap NAS中Docker的服务&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10/dist/style.css&#34;&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/d3@7&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-view@0.18.10&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10&#34;&gt;&lt;/script&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;/css/markmap.css&#34;&gt;

&lt;script src=&#34;/js/markmap.js&#34;&gt;&lt;/script&gt;
</content>
        <category term="docker" />
        <category term="earthly" />
        <updated>2021-10-31T23:25:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.17lai.site/posts/465d2738/</id>
        <title>如何使用Traefik V2 在Ubuntu20.04 上面来做 Dockers Containers 的反向代理</title>
        <link rel="alternate" href="https://blog.17lai.site/posts/465d2738/"/>
        <content type="html">&lt;p&gt;How To Use Traefik v2 as a Reverse Proxy for Docker Containers on Ubuntu 20.04&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Traefik 适合配合Dockers swarm 做服务， Dockers portainer 做管理，ELK集群做监控日志。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/10/1420211014200800.png&#34; alt=&#34;Traefik&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;traefik&lt;/code&gt; 与 &lt;code&gt;nginx&lt;/code&gt; 一样，是一款优秀的反向代理工具，或者叫 &lt;code&gt;Edge Router&lt;/code&gt;。至于使用它的原因则基于以下几点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无须重启即可更新配置&lt;/li&gt;
&lt;li&gt;自动的服务发现与负载均衡&lt;/li&gt;
&lt;li&gt;与 &lt;code&gt;docker&lt;/code&gt; 完美集成，基于 &lt;code&gt;container label&lt;/code&gt; 的配置&lt;/li&gt;
&lt;li&gt;漂亮的 &lt;code&gt;dashboard&lt;/code&gt; 界面&lt;/li&gt;
&lt;li&gt;&lt;code&gt;metrics&lt;/code&gt; 的支持，支持对 &lt;code&gt;prometheus&lt;/code&gt; 和 &lt;code&gt;k8s&lt;/code&gt; 集成&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;Introduction&#34;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://www.docker.com/&#34;&gt;Docker&lt;/a&gt; can be an efficient way to run web applications in production, but you may want to run multiple applications on the same Docker host. In this situation, you’ll need to set up a reverse proxy. This is because you only want to expose ports &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt; to the rest of the world.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://traefik.io/&#34;&gt;Traefik&lt;/a&gt; is a Docker-aware reverse proxy that includes a monitoring dashboard. Traefik v1 has been widely used for a while, and &lt;a href=&#34;https://www.digitalocean.com/community/tutorials/how-to-use-traefik-as-a-reverse-proxy-for-docker-containers-on-ubuntu-20-04&#34;&gt;you can follow this earlier tutorial to install Traefik v1&lt;/a&gt;). But in this tutorial, you’ll install and configure Traefik v2, which includes quite a few differences.&lt;/p&gt;
&lt;p&gt;The biggest difference between Traefik v1 and v2 is that &lt;em&gt;frontends&lt;/em&gt; and &lt;em&gt;backends&lt;/em&gt; were removed and their combined functionality spread out across &lt;em&gt;routers&lt;/em&gt;, &lt;em&gt;middlewares&lt;/em&gt;, and &lt;em&gt;services&lt;/em&gt;. Previously a backend did the job of making modifications to requests and getting that request to whatever was supposed to handle it. Traefik v2 provides more separation of concerns by introducing middlewares that can modify requests before sending them to a service. Middlewares make it easier to specify a single modification step that might be used by a lot of different routes so that they can be reused (such as HTTP Basic Auth, which you’ll see later). A router can also use many different middlewares.&lt;/p&gt;
&lt;p&gt;In this tutorial you’ll configure Traefik v2 to route requests to two different web application containers: a &lt;a href=&#34;http://wordpress.org/&#34;&gt;Wordpress&lt;/a&gt; container and an &lt;a href=&#34;https://www.adminer.org/&#34;&gt;Adminer&lt;/a&gt; container, each talking to a &lt;a href=&#34;https://www.mysql.com/&#34;&gt;MySQL&lt;/a&gt; database. You’ll configure Traefik to serve everything over HTTPS using &lt;a href=&#34;https://letsencrypt.org/&#34;&gt;Let’s Encrypt&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;Prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;To complete this tutorial, you will need the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.digitalocean.com/products/linux-distribution/ubuntu/&#34;&gt;One Ubuntu 20.04 server&lt;/a&gt; with a sudo non-root user and a firewall. You can set this up by following our &lt;a href=&#34;https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-20-04&#34;&gt;Ubuntu 20.04 initial server setup guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Docker installed on your server, which you can accomplish by following &lt;strong&gt;Steps 1 and 2&lt;/strong&gt; of &lt;a href=&#34;https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04&#34;&gt;How to Install and Use Docker on Ubuntu 20.04&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Docker Compose installed using the instructions from &lt;strong&gt;Step 1&lt;/strong&gt; of &lt;a href=&#34;https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-compose-on-ubuntu-20-04&#34;&gt;How to Install Docker Compose on Ubuntu 20.04&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A domain and three A records, &lt;code&gt;db-admin.your_domain&lt;/code&gt;, &lt;code&gt;blog.your_domain&lt;/code&gt; and &lt;code&gt;monitor.your_domain&lt;/code&gt;. Each should point to the IP address of your server. You can learn how to point domains to DigitalOcean Droplets by reading through &lt;a href=&#34;https://www.digitalocean.com/docs/networking/dns/&#34;&gt;DigitalOcean’s Domains and DNS documentation&lt;/a&gt;. Throughout this tutorial, substitute your domain for &lt;code&gt;your_domain&lt;/code&gt; in the configuration files and examples.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;Step-1-—-Configuring-and-Running-Traefik&#34;&gt;Step 1 — Configuring and Running Traefik&lt;/h2&gt;
&lt;p&gt;The Traefik project has an &lt;a href=&#34;https://hub.docker.com/_/traefik&#34;&gt;official Docker image&lt;/a&gt;, so you will use that to run Traefik in a Docker container.&lt;/p&gt;
&lt;p&gt;But before you get your Traefik container up and running, you need to create a configuration file and set up an encrypted password so you can access the monitoring dashboard.&lt;/p&gt;
&lt;p&gt;You’ll use the &lt;code&gt;htpasswd&lt;/code&gt; utility to create this encrypted password. First, install the utility, which is included in the &lt;code&gt;apache2-utils&lt;/code&gt; package:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;sudo apt-get install apache2-utils&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Then generate the password with &lt;code&gt;htpasswd&lt;/code&gt;. Substitute &lt;code&gt;secure_password&lt;/code&gt; with the password you’d like to use for the Traefik admin user:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;htpasswd -nb admin secure_password&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;The output from the program will look like this:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-none&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;Outputadmin:$apr1$ruca84Hq$mbjdMZBAG.KWn7vfN&amp;#x2F;SNK&amp;#x2F;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;You’ll use this output in the Traefik configuration file to set up HTTP Basic Authentication for the Traefik health check and monitoring dashboard. Copy the entire output line so you can paste it later.&lt;/p&gt;
&lt;p&gt;To configure the Traefik server, you’ll create two new configuration files called &lt;code&gt;traefik.toml&lt;/code&gt; and &lt;code&gt;traefik_dynamic.toml&lt;/code&gt; using the TOML format. &lt;a href=&#34;https://github.com/toml-lang/toml&#34;&gt;TOML&lt;/a&gt; is a configuration language similar to INI files, but standardized. &lt;a href=&#34;https://docs.traefik.io/providers/overview/&#34;&gt;These files let us configure the Traefik server and various integrations&lt;/a&gt;, or &lt;code&gt;providers&lt;/code&gt;, that you want to use. In this tutorial, you will use three of Traefik’s available providers: &lt;code&gt;api&lt;/code&gt;, &lt;code&gt;docker&lt;/code&gt;, and &lt;code&gt;acme&lt;/code&gt;. The last of these, &lt;code&gt;acme&lt;/code&gt;, supports TLS certificates using Let’s Encrypt.&lt;/p&gt;
&lt;p&gt;Create and open &lt;code&gt;traefik.toml&lt;/code&gt; using &lt;code&gt;nano&lt;/code&gt; or your preferred text editor:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;nano traefik.toml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;First, you want to specify the ports that Traefik should listen on using the &lt;code&gt;entryPoints&lt;/code&gt; section of your config file. You want two because you want to listen on port &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt;. Let’s call these &lt;code&gt;web&lt;/code&gt; (port &lt;code&gt;80&lt;/code&gt;) and &lt;code&gt;websecure&lt;/code&gt; (port &lt;code&gt;443&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Add the following configurations:&lt;/p&gt;
&lt;p&gt;traefik.toml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-toml&#34; data-language=&#34;toml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-toml&#34;&gt;[entryPoints]
  [entryPoints.web]
    address &amp;#x3D; &amp;quot;:80&amp;quot;
    [entryPoints.web.http.redirections.entryPoint]
      to &amp;#x3D; &amp;quot;websecure&amp;quot;
      scheme &amp;#x3D; &amp;quot;https&amp;quot;

  [entryPoints.websecure]
    address &amp;#x3D; &amp;quot;:443&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Note that you are also automatically redirecting traffic to be handled over TLS.&lt;/p&gt;
&lt;p&gt;Next, configure the Traefik &lt;code&gt;api&lt;/code&gt;, which gives you access to both the API and your dashboard interface. The heading of &lt;code&gt;[api]&lt;/code&gt; is all that you need because the dashboard is then enabled by default, but you’ll be explicit for the time being.&lt;/p&gt;
&lt;p&gt;Add the following code:&lt;/p&gt;
&lt;p&gt;traefik.toml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-toml&#34; data-language=&#34;toml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-toml&#34;&gt;...
[api]
  dashboard &amp;#x3D; true&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;To finish securing your web requests you want to use Let’s Encrypt to generate valid TLS certificates. Traefik v2 supports Let’s Encrypt out of the box and you can configure it by creating a &lt;em&gt;certificates resolver&lt;/em&gt; of the type &lt;code&gt;acme&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let’s configure your certificates resolver now using the name &lt;code&gt;lets-encrypt&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;traefik.toml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-toml&#34; data-language=&#34;toml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-toml&#34;&gt;...
[certificatesResolvers.lets-encrypt.acme]
  email &amp;#x3D; &amp;quot;your_email@your_domain&amp;quot;
  storage &amp;#x3D; &amp;quot;acme.json&amp;quot;
  [certificatesResolvers.lets-encrypt.acme.tlsChallenge]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;This section is called &lt;code&gt;acme&lt;/code&gt; because &lt;a href=&#34;https://github.com/ietf-wg-acme/acme/&#34;&gt;ACME&lt;/a&gt; is the name of the protocol used to communicate with Let’s Encrypt to manage certificates. The Let’s Encrypt service requires registration with a valid email address, so to have Traefik generate certificates for your hosts, set the &lt;code&gt;email&lt;/code&gt; key to your email address. You then specify that you will store the information that you will receive from Let’s Encrypt in a JSON file called &lt;code&gt;acme.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;acme.tlsChallenge&lt;/code&gt; section allows us to specify how Let’s Encrypt can verify that the certificate. You’re configuring it to serve a file as part of the challenge over port &lt;code&gt;443&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, you need to configure Traefik to work with Docker.&lt;/p&gt;
&lt;p&gt;Add the following configurations:&lt;/p&gt;
&lt;p&gt;traefik.toml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-toml&#34; data-language=&#34;toml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-toml&#34;&gt;...
[providers.docker]
  watch &amp;#x3D; true
  network &amp;#x3D; &amp;quot;web&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;docker&lt;/code&gt; provider enables Traefik to act as a proxy in front of Docker containers. You’ve configured the provider to &lt;code&gt;watch&lt;/code&gt; for new containers on the &lt;code&gt;web&lt;/code&gt; network, which you’ll create soon.&lt;/p&gt;
&lt;p&gt;Our final configuration uses the &lt;code&gt;file&lt;/code&gt; provider. With Traefik v2, static and dynamic configurations can’t be mixed and matched. To get around this, you will use &lt;code&gt;traefik.toml&lt;/code&gt; to define your static configurations and then keep your dynamic configurations in another file, which you will call &lt;code&gt;traefik_dynamic.toml&lt;/code&gt;. Here you are using the &lt;code&gt;file&lt;/code&gt; provider to tell Traefik that it should read in dynamic configurations from a different file.&lt;/p&gt;
&lt;p&gt;Add the following &lt;code&gt;file&lt;/code&gt; provider:&lt;/p&gt;
&lt;p&gt;traefik.toml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;[providers.file]
  filename &amp;#x3D; &amp;quot;traefik_dynamic.toml&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Your completed &lt;code&gt;traefik.toml&lt;/code&gt; will look like this:&lt;/p&gt;
&lt;p&gt;traefik.toml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-toml&#34; data-language=&#34;toml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-toml&#34;&gt;[entryPoints]
  [entryPoints.web]
    address &amp;#x3D; &amp;quot;:80&amp;quot;
    [entryPoints.web.http.redirections.entryPoint]
      to &amp;#x3D; &amp;quot;websecure&amp;quot;
      scheme &amp;#x3D; &amp;quot;https&amp;quot;

  [entryPoints.websecure]
    address &amp;#x3D; &amp;quot;:443&amp;quot;

[api]
  dashboard &amp;#x3D; true

[certificatesResolvers.lets-encrypt.acme]
  email &amp;#x3D; &amp;quot;your_email@your_domain&amp;quot;
  storage &amp;#x3D; &amp;quot;acme.json&amp;quot;
  [certificatesResolvers.lets-encrypt.acme.tlsChallenge]

[providers.docker]
  watch &amp;#x3D; true
  network &amp;#x3D; &amp;quot;web&amp;quot;

[providers.file]
  filename &amp;#x3D; &amp;quot;traefik_dynamic.toml&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Save and close the file.&lt;/p&gt;
&lt;p&gt;Now let’s create &lt;code&gt;traefik_dynamic.toml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The dynamic configuration values that you need to keep in their own file are the &lt;em&gt;middlewares&lt;/em&gt; and the &lt;em&gt;routers&lt;/em&gt;. To put your dashboard behind a password you need to customize the API’s &lt;em&gt;router&lt;/em&gt; and configure a &lt;em&gt;middleware&lt;/em&gt; to handle HTTP basic authentication. Let’s start by setting up the middleware.&lt;/p&gt;
&lt;p&gt;The middleware is configured on a per-protocol basis and since you’re working with HTTP you’ll specify it as a section chained off of &lt;code&gt;http.middlewares&lt;/code&gt;. Next comes the name of your middleware so that you can reference it later, followed by the type of middleware that it is, which will be &lt;code&gt;basicAuth&lt;/code&gt; in this case. Let’s call your middleware &lt;code&gt;simpleAuth&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create and open a new file called &lt;code&gt;traefik_dynamic.toml&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;nano traefik_dynamic.toml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Add the following code. This is where you’ll paste the output from the &lt;code&gt;htpasswd&lt;/code&gt; command:&lt;/p&gt;
&lt;p&gt;traefik_dynamic.toml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-toml&#34; data-language=&#34;toml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-toml&#34;&gt;[http.middlewares.simpleAuth.basicAuth]
  users &amp;#x3D; [
    &amp;quot;admin:$apr1$ruca84Hq$mbjdMZBAG.KWn7vfN&amp;#x2F;SNK&amp;#x2F;&amp;quot;
  ]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;To configure the router for the api you’ll once again be chaining off of the protocol name, but instead of using &lt;code&gt;http.middlewares&lt;/code&gt;, you’ll use &lt;code&gt;http.routers&lt;/code&gt; followed by the name of the router. In this case, the &lt;code&gt;api&lt;/code&gt; provides its own named router that you can configure by using the &lt;code&gt;[http.routers.api]&lt;/code&gt; section. You’ll configure the domain that you plan on using with your dashboard also by setting the &lt;code&gt;rule&lt;/code&gt; key using a host match, the entrypoint to use &lt;code&gt;websecure&lt;/code&gt;, and the middlewares to include &lt;code&gt;simpleAuth&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Add the following configurations:&lt;/p&gt;
&lt;p&gt;traefik_dynamic.toml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-toml&#34; data-language=&#34;toml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-toml&#34;&gt;...
[http.routers.api]
  rule &amp;#x3D; &amp;quot;Host(&amp;#96;monitor.your_domain&amp;#96;)&amp;quot;
  entrypoints &amp;#x3D; [&amp;quot;websecure&amp;quot;]
  middlewares &amp;#x3D; [&amp;quot;simpleAuth&amp;quot;]
  service &amp;#x3D; &amp;quot;api@internal&amp;quot;
  [http.routers.api.tls]
    certResolver &amp;#x3D; &amp;quot;lets-encrypt&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;web&lt;/code&gt; entry point handles port &lt;code&gt;80&lt;/code&gt;, while the &lt;code&gt;websecure&lt;/code&gt; entry point uses port &lt;code&gt;443&lt;/code&gt; for TLS/SSL. You automatically redirect all of the traffic on port &lt;code&gt;80&lt;/code&gt; to the &lt;code&gt;websecure&lt;/code&gt; entry point to force secure connections for all requests.&lt;/p&gt;
&lt;p&gt;Notice the last three lines here configure a &lt;em&gt;service&lt;/em&gt;, enable tls, and configure &lt;code&gt;certResolver&lt;/code&gt; to &lt;code&gt;&amp;quot;lets-encrypt&amp;quot;&lt;/code&gt;. Services are the final step to determining where a request is finally handled. The &lt;code&gt;api@internal&lt;/code&gt; service is a built-in service that sits behind the API that you expose. Just like routers and middlewares, services can be configured in this file, but you won’t need to do that to achieve your desired result.&lt;/p&gt;
&lt;p&gt;Your completed &lt;code&gt;traefik_dynamic.toml&lt;/code&gt; file will look like this:&lt;/p&gt;
&lt;p&gt;traefik_dynamic.toml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-toml&#34; data-language=&#34;toml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-toml&#34;&gt;[http.middlewares.simpleAuth.basicAuth]
  users &amp;#x3D; [
    &amp;quot;admin:$apr1$ruca84Hq$mbjdMZBAG.KWn7vfN&amp;#x2F;SNK&amp;#x2F;&amp;quot;
  ]

[http.routers.api]
  rule &amp;#x3D; &amp;quot;Host(&amp;#96;monitor.your_domain&amp;#96;)&amp;quot;
  entrypoints &amp;#x3D; [&amp;quot;websecure&amp;quot;]
  middlewares &amp;#x3D; [&amp;quot;simpleAuth&amp;quot;]
  service &amp;#x3D; &amp;quot;api@internal&amp;quot;
  [http.routers.api.tls]
    certResolver &amp;#x3D; &amp;quot;lets-encrypt&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Save the file and exit the editor.&lt;/p&gt;
&lt;p&gt;With these configurations in place, you will now start Traefik.&lt;/p&gt;
&lt;h2 id=&#34;Step-2-–-Running-the-Traefik-Container&#34;&gt;Step 2 – Running the Traefik Container&lt;/h2&gt;
&lt;p&gt;In this step you will create a Docker network for the proxy to share with containers. You will then access the Traefik dashboard. The Docker network is necessary so that you can use it with applications that are run using Docker Compose.&lt;/p&gt;
&lt;p&gt;Create a new Docker network called &lt;code&gt;web&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker network create web&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;When the Traefik container starts, you will add it to this network. Then you can add additional containers to this network later for Traefik to proxy to.&lt;/p&gt;
&lt;p&gt;Next, create an empty file that will hold your Let’s Encrypt information. You’ll share this into the container so Traefik can use it:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;touch acme.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Traefik will only be able to use this file if the root user inside of the container has unique read and write access to it. To do this, lock down the permissions on &lt;code&gt;acme.json&lt;/code&gt; so that only the owner of the file has read and write permission.&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;chmod 600 acme.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Once the file gets passed to Docker, the owner will automatically change to the &lt;strong&gt;root&lt;/strong&gt; user inside the container.&lt;/p&gt;
&lt;p&gt;Finally, create the Traefik container with this command:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker run -d \
  -v &amp;#x2F;var&amp;#x2F;run&amp;#x2F;docker.sock:&amp;#x2F;var&amp;#x2F;run&amp;#x2F;docker.sock \
  -v $PWD&amp;#x2F;traefik.toml:&amp;#x2F;traefik.toml \
  -v $PWD&amp;#x2F;traefik_dynamic.toml:&amp;#x2F;traefik_dynamic.toml \
  -v $PWD&amp;#x2F;acme.json:&amp;#x2F;acme.json \
  -p 80:80 \
  -p 443:443 \
  --network web \
  --name traefik \
  traefik:v2.2&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;This command is a little long. Let’s break it down.&lt;/p&gt;
&lt;p&gt;You use the &lt;code&gt;-d&lt;/code&gt; flag to run the container in the background as a daemon. You then share your &lt;code&gt;docker.sock&lt;/code&gt; file into the container so that the Traefik process can listen for changes to containers. You also share the &lt;code&gt;traefik.toml&lt;/code&gt; and &lt;code&gt;traefik_dynamic.toml&lt;/code&gt; configuration files into the container, as well as &lt;code&gt;acme.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, you map ports &lt;code&gt;:80&lt;/code&gt; and &lt;code&gt;:443&lt;/code&gt; of your Docker host to the same ports in the Traefik container so Traefik receives all HTTP and HTTPS traffic to the server.&lt;/p&gt;
&lt;p&gt;You set the network of the container to &lt;code&gt;web&lt;/code&gt;, and you name the container &lt;code&gt;traefik&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, you use the &lt;code&gt;traefik:v2.2&lt;/code&gt; image for this container so that you can guarantee that you’re not running a completely different version than this tutorial is written for.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.docker.com/engine/reference/builder/#entrypoint&#34;&gt;A Docker image’s &lt;code&gt;ENTRYPOINT&lt;/code&gt; is a command that always runs when a container is created from the image&lt;/a&gt;. In this case, the command is the &lt;code&gt;traefik&lt;/code&gt; binary within the container. You can pass additional arguments to that command when you launch the container, but you’ve configured all of your settings in the &lt;code&gt;traefik.toml&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;With the container started, you now have a dashboard you can access to see the health of your containers. You can also use this dashboard to visualize the routers, services, and middlewares that Traefik has registered. You can try to access the monitoring dashboard by pointing your browser to &lt;code&gt;https://monitor.your_domain/dashboard/&lt;/code&gt; (the trailing &lt;code&gt;/&lt;/code&gt; is required).&lt;/p&gt;
&lt;p&gt;You will be prompted for your username and password, which are &lt;strong&gt;admin&lt;/strong&gt; and the password you configured in Step 1.&lt;/p&gt;
&lt;p&gt;Once logged in, you’ll see the Traefik interface:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/10/1420211014200207.png&#34; alt=&#34;Empty Traefik dashboard&#34;&gt;&lt;/p&gt;
&lt;p&gt;You will notice that there are already some routers and services registered, but those are the ones that come with Traefik and the router configuration that you wrote for the API.&lt;/p&gt;
&lt;p&gt;You now have your Traefik proxy running, and you’ve configured it to work with Docker and monitor other containers. In the next step you will start some containers for Traefik to proxy.&lt;/p&gt;
&lt;h2 id=&#34;Step-3-—-Registering-Containers-with-Traefik&#34;&gt;Step 3 — Registering Containers with Traefik&lt;/h2&gt;
&lt;p&gt;With the Traefik container running, you’re ready to run applications behind it. Let’s launch the following containers behind Traefik:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A blog using the &lt;a href=&#34;https://hub.docker.com/_/wordpress/&#34;&gt;official WordPress image&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A database management server using the &lt;a href=&#34;https://hub.docker.com/_/adminer/&#34;&gt;official Adminer image&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You’ll manage both of these applications with Docker Compose using a &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Create and open the &lt;code&gt;docker-compose.yml&lt;/code&gt; file in your editor:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;nano docker-compose.yml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Add the following lines to the file to specify the version and the networks you’ll use:&lt;/p&gt;
&lt;p&gt;docker-compose.yml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-yaml&#34; data-language=&#34;yaml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;version: &amp;quot;3&amp;quot;

networks:
  web:
    external: true
  internal:
    external: false&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;You use Docker Compose version &lt;code&gt;3&lt;/code&gt; because it’s the newest major version of the Compose file format.&lt;/p&gt;
&lt;p&gt;For Traefik to recognize your applications, they must be part of the same network, and since you created the network manually, you pull it in by specifying the network name of &lt;code&gt;web&lt;/code&gt; and setting &lt;code&gt;external&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;. Then you define another network so that you can connect your exposed containers to a database container that you won’t expose through Traefik. You’ll call this network &lt;code&gt;internal&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, you’ll define each of your &lt;code&gt;services&lt;/code&gt;, one at a time. Let’s start with the &lt;code&gt;blog&lt;/code&gt; container, which you’ll base on the official WordPress image. Add this configuration to the bottom of the file:&lt;/p&gt;
&lt;p&gt;docker-compose.yml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-yaml&#34; data-language=&#34;yaml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;...

services:
  blog:
    image: wordpress:4.9.8-apache
    environment:
      WORDPRESS_DB_PASSWORD:
    labels:
      - traefik.http.routers.blog.rule&amp;#x3D;Host(&amp;#96;blog.your_domain&amp;#96;)
      - traefik.http.routers.blog.tls&amp;#x3D;true
      - traefik.http.routers.blog.tls.certresolver&amp;#x3D;lets-encrypt
      - traefik.port&amp;#x3D;80
    networks:
      - internal
      - web
    depends_on:
      - mysql&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;environment&lt;/code&gt; key lets you specify environment variables that will be set inside of the container. By not setting a value for &lt;code&gt;WORDPRESS_DB_PASSWORD&lt;/code&gt;, you’re telling Docker Compose to get the value from your shell and pass it through when you create the container. You will define this environment variable in your shell before starting the containers. This way you don’t hard-code passwords into the configuration file.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;labels&lt;/code&gt; section is where you specify configuration values for Traefik. Docker labels don’t do anything by themselves, but Traefik reads these so it knows how to treat containers. Here’s what each of these labels does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;traefik.http.routers.adminer.rule=Host(`````blog.your_domain`````)&lt;/code&gt; creates a new &lt;em&gt;router&lt;/em&gt; for your container and then specifies the routing rule used to determine if a request matches this container.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;traefik.routers.custom_name.tls=true&lt;/code&gt; specifies that this router should use TLS.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;traefik.routers.custom_name.tls.certResolver=lets-encrypt&lt;/code&gt; specifies that the certificates resolver that you created earlier called &lt;code&gt;lets-encrypt&lt;/code&gt; should be used to get a certificate for this route.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;traefik.port&lt;/code&gt; specifies the exposed port that Traefik should use to route traffic to this container.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this configuration, all traffic sent to your Docker host on port &lt;code&gt;80&lt;/code&gt; or &lt;code&gt;443&lt;/code&gt; with the domain of &lt;code&gt;blog.your_domain&lt;/code&gt; will be routed to the &lt;code&gt;blog&lt;/code&gt; container.&lt;/p&gt;
&lt;p&gt;You assign this container to two different networks so that Traefik can find it via the &lt;code&gt;web&lt;/code&gt; network and it can communicate with the database container through the &lt;code&gt;internal&lt;/code&gt; network.&lt;/p&gt;
&lt;p&gt;Lastly, the &lt;code&gt;depends_on&lt;/code&gt; key tells Docker Compose that this container needs to start &lt;em&gt;after&lt;/em&gt; its dependencies are running. Since WordPress needs a database to run, you must run your &lt;code&gt;mysql&lt;/code&gt; container before starting your &lt;code&gt;blog&lt;/code&gt; container.&lt;/p&gt;
&lt;p&gt;Next, configure the MySQL service:&lt;/p&gt;
&lt;p&gt;docker-compose.yml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-yaml&#34; data-language=&#34;yaml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;services:
...
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD:
    networks:
      - internal
    labels:
      - traefik.enable&amp;#x3D;false&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;You’re using the official MySQL 5.7 image for this container. You’ll notice that you’re once again using an &lt;code&gt;environment&lt;/code&gt; item without a value. The &lt;code&gt;MYSQL_ROOT_PASSWORD&lt;/code&gt; and &lt;code&gt;WORDPRESS_DB_PASSWORD&lt;/code&gt; variables will need to be set to the same value to make sure that your WordPress container can communicate with the MySQL. You don’t want to expose the &lt;code&gt;mysql&lt;/code&gt; container to Traefik or the outside world, so you’re only assigning this container to the &lt;code&gt;internal&lt;/code&gt; network. Since Traefik has access to the Docker socket, the process will still expose a router for the &lt;code&gt;mysql&lt;/code&gt; container by default, so you’ll add the label &lt;code&gt;traefik.enable=false&lt;/code&gt; to specify that Traefik should not expose this container.&lt;/p&gt;
&lt;p&gt;Finally, define the Adminer container:&lt;/p&gt;
&lt;p&gt;docker-compose.yml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-yaml&#34; data-language=&#34;yaml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;services:
...
  adminer:
    image: adminer:4.6.3-standalone
    labels:
      - traefik.http.routers.adminer.rule&amp;#x3D;Host(&amp;#96;db-admin.your_domain&amp;#96;)
      - traefik.http.routers.adminer.tls&amp;#x3D;true
      - traefik.http.routers.adminer.tls.certresolver&amp;#x3D;lets-encrypt
      - traefik.port&amp;#x3D;8080
    networks:
      - internal
      - web
    depends_on:
      - mysql&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;This container is based on the official Adminer image. The &lt;code&gt;network&lt;/code&gt; and &lt;code&gt;depends_on&lt;/code&gt; configuration for this container exactly match what you’re using for the &lt;code&gt;blog&lt;/code&gt; container.&lt;/p&gt;
&lt;p&gt;The line &lt;code&gt;traefik.http.routers.adminer.rule=Host(`````db-admin.your_domain`````)&lt;/code&gt; tells Traefik to examine the host requested. If it matches the pattern of &lt;code&gt;db-admin.your_domain&lt;/code&gt;, Traefik will route the traffic to the &lt;code&gt;adminer&lt;/code&gt; container over port &lt;code&gt;8080&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Your completed &lt;code&gt;docker-compose.yml&lt;/code&gt; file will look like this:&lt;/p&gt;
&lt;p&gt;docker-compose.yml&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-yaml&#34; data-language=&#34;yaml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;version: &amp;quot;3&amp;quot;

networks:
  web:
    external: true
  internal:
    external: false

services:
  blog:
    image: wordpress:4.9.8-apache
    environment:
      WORDPRESS_DB_PASSWORD:
    labels:
      - traefik.http.routers.blog.rule&amp;#x3D;Host(&amp;#96;blog.your_domain&amp;#96;)
      - traefik.http.routers.blog.tls&amp;#x3D;true
      - traefik.http.routers.blog.tls.certresolver&amp;#x3D;lets-encrypt
      - traefik.port&amp;#x3D;80
    networks:
      - internal
      - web
    depends_on:
      - mysql

  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD:
    networks:
      - internal
    labels:
      - traefik.enable&amp;#x3D;false

  adminer:
    image: adminer:4.6.3-standalone
    labels:
    labels:
      - traefik.http.routers.adminer.rule&amp;#x3D;Host(&amp;#96;db-admin.your_domain&amp;#96;)
      - traefik.http.routers.adminer.tls&amp;#x3D;true
      - traefik.http.routers.adminer.tls.certresolver&amp;#x3D;lets-encrypt
      - traefik.port&amp;#x3D;8080
    networks:
      - internal
      - web
    depends_on:
      - mysql&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Save the file and exit the text editor.&lt;/p&gt;
&lt;p&gt;Next, set values in your shell for the &lt;code&gt;WORDPRESS_DB_PASSWORD&lt;/code&gt; and &lt;code&gt;MYSQL_ROOT_PASSWORD&lt;/code&gt; variables:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;export WORDPRESS_DB_PASSWORD&amp;#x3D;secure_database_password
export MYSQL_ROOT_PASSWORD&amp;#x3D;secure_database_password&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Substitute &lt;code&gt;secure_database_password&lt;/code&gt; with your desired database password. Remember to use the same password for both &lt;code&gt;WORDPRESS_DB_PASSWORD&lt;/code&gt; and &lt;code&gt;MYSQL_ROOT_PASSWORD&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With these variables set, run the containers using &lt;code&gt;docker-compose&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker-compose up -d&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Now watch the Traefik admin dashboard while it populates.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/10/1420211014200110.png&#34; alt=&#34;Populated Traefik dashboard&#34;&gt;&lt;/p&gt;
&lt;p&gt;If you explore the &lt;strong&gt;Routers&lt;/strong&gt; section you will find routers for &lt;code&gt;adminer&lt;/code&gt; and &lt;code&gt;blog&lt;/code&gt; configured with TLS:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/10/1420211014200142.png&#34; alt=&#34;HTTP Routers w/ TLS&#34;&gt;&lt;/p&gt;
&lt;p&gt;Navigate to &lt;code&gt;blog.your_domain&lt;/code&gt;, substituting &lt;code&gt;your_domain&lt;/code&gt; with your domain. You’ll be redirected to a TLS connection and you can now complete the WordPress setup:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/10/1420211014200114.png&#34; alt=&#34;WordPress setup screen&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now access Adminer by visiting &lt;code&gt;db-admin.your_domain&lt;/code&gt; in your browser, again substituting &lt;code&gt;your_domain&lt;/code&gt; with your domain. The &lt;code&gt;mysql&lt;/code&gt; container isn’t exposed to the outside world, but the &lt;code&gt;adminer&lt;/code&gt; container has access to it through the &lt;code&gt;internal&lt;/code&gt; Docker network that they share using the &lt;code&gt;mysql&lt;/code&gt; container name as a hostname.&lt;/p&gt;
&lt;p&gt;On the Adminer login screen, enter &lt;code&gt;root&lt;/code&gt; for &lt;strong&gt;Username&lt;/strong&gt;, enter &lt;code&gt;mysql&lt;/code&gt; for &lt;strong&gt;Server&lt;/strong&gt;, and enter the value you set for &lt;code&gt;MYSQL_ROOT_PASSWORD&lt;/code&gt; for the &lt;strong&gt;Password&lt;/strong&gt;. Leave &lt;strong&gt;Database&lt;/strong&gt; empty. Now press &lt;strong&gt;Login&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Once logged in, you’ll see the Adminer user interface.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/10/1420211014200132.png&#34; alt=&#34;Adminer connected to the MySQL database&#34;&gt;&lt;/p&gt;
&lt;p&gt;Both sites are now working, and you can use the dashboard at &lt;code&gt;monitor.your_domain&lt;/code&gt; to keep an eye on your applications.&lt;/p&gt;
&lt;h2 id=&#34;Conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this tutorial, you configured Traefik v2 to proxy requests to other applications in Docker containers.&lt;/p&gt;
&lt;p&gt;Traefik’s declarative configuration at the application container level makes it easy to configure more services, and there’s no need to restart the &lt;code&gt;traefik&lt;/code&gt; container when you add new applications to proxy traffic to since Traefik notices the changes immediately through the Docker socket file it’s monitoring.&lt;/p&gt;
&lt;p&gt;To learn more about what you can do with Traefik v2, head over to &lt;a href=&#34;https://doc.traefik.io/traefik/&#34;&gt;the official Traefik documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;服务集群&#34;&gt;服务集群&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;k8s 太重了,虽然也有 k3s 之类的轻量级 k8s 解决方案，不过我还是选择了原生的 docker swarm。VPS 安装好 Docker 之后，不需要额外安装软件，就可以马上建立集群。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;# 集群初始化，节点成为 manager 节点
docker swarm init --advertise-addr&amp;#x3D;x.x.x.x

# 集群丢失 Leader 时，强制重建集群
docker swarm init --advertise-addr&amp;#x3D;x.x.x.x --force-new-cluster

# 获取作为 worker 节点加入集群的命令
docker swarm join-token worker

# 获取作为 manager 节点加入集群的命令
docker swarm join-token manager

# 加入集群
docker swarm join --token xxx x.x.x.x:xxx --advertise-addr&amp;#x3D;x.x.x.x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h2 id=&#34;参考：&#34;&gt;参考：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.digitalocean.com/community/tutorials/how-to-use-traefik-v2-as-a-reverse-proxy-for-docker-containers-on-ubuntu-20-04&#34;&gt;digitalocean&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://shanyue.tech/op/traefik.html&#34;&gt;shanyue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;系列教程&#34;&gt;&lt;strong&gt;系列教程&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;全部文章RSS订阅&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;Docker系列&#34;&gt;&lt;strong&gt;Docker系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/docker/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;Docker 分类 RSS 订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/42b6a86d/&#34;&gt;Docker使用简明教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/9912bd5d/&#34;&gt;使用jeckett,sonarr,iyuu,qt,emby打造全自动追剧流程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/1802a8a7/&#34;&gt;为知笔记私有化Docker部署&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/593cc323/&#34;&gt;Earthly 一个更加强大的镜像构建工具&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/90e60aac/&#34;&gt;使用 Shell 脚本实现一个简单 Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/465d2738/&#34;&gt;如何使用Traefik V2 在Ubuntu20.04 上面来做 Dockers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/462f1e5c/&#34;&gt;通过IPV6访问Qnap NAS中Docker的服务&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10/dist/style.css&#34;&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/d3@7&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-view@0.18.10&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10&#34;&gt;&lt;/script&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;/css/markmap.css&#34;&gt;

&lt;script src=&#34;/js/markmap.js&#34;&gt;&lt;/script&gt;
</content>
        <category term="docker" />
        <category term="traefik" />
        <category term="proxy" />
        <category term="swarm" />
        <category term="ubuntu" />
        <updated>2021-10-14T11:25:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.17lai.site/posts/462f1e5c/</id>
        <title>如何通过IPV6访问Qnap NAS中Docker的服务</title>
        <link rel="alternate" href="https://blog.17lai.site/posts/462f1e5c/"/>
        <content type="html">&lt;blockquote&gt;
&lt;p&gt;目前找到了两个解决方案&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;socat 端口转发&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/robbertkl/docker-ipv6nat&#34;&gt;docker-ipv6nat&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;很早就和QNAP官方反馈请求支持IPV6，但一直没反应&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;socat-端口转发&#34;&gt;socat 端口转发&lt;/h2&gt;
&lt;h3 id=&#34;环境&#34;&gt;环境&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;系统：QTS 4.3.6&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;网络：IPV4 &amp;amp; IPV6&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Docker: 由Container Station提供&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;问题&#34;&gt;问题&lt;/h3&gt;
&lt;p&gt;通过ipv6地址可以打开NAS的管理页面，但是无法访问Docker对应端口的服务。&lt;/p&gt;
&lt;h3 id=&#34;排查&#34;&gt;排查&lt;/h3&gt;
&lt;p&gt;QTS中Docker使用的虚拟交换机网络没有启动IPV6，且无法在虚拟交换机设置中手动启动。这样一来，Docker只监听了tcp4的端口，对于主机上tcp6的端口的访问无法映射到docker容器上。&lt;/p&gt;
&lt;h3 id=&#34;解决方案&#34;&gt;解决方案&lt;/h3&gt;
&lt;p&gt;在主机上开一个tcp6的端口，将其转发到主机上与docker关联的tcp4端口。即：&lt;br&gt;
docker(tcp4)–&amp;gt;host(tcp4)–&amp;gt;host(tcp6)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在qts上安装包管理器：Entware. &lt;a href=&#34;https://github.com/Entware/Entware/wiki/Install-on-QNAP-NAS&#34;&gt;https://github.com/Entware/Entware/wiki/Install-on-QNAP-NAS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;执行&lt;code&gt;opkg update&lt;/code&gt;,更新&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安装端口转发工具，这里使用socat：&lt;code&gt;opkg install socat&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设置转发host(tcp6)–&amp;gt;host(tcp4)&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;socat TCP6-LISTEN:6880,reuseaddr,fork TCP4:127.0.0.1:7880 &amp;amp;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;大功告成&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;docker-ipv6nat&#34;&gt;&lt;a href=&#34;https://github.com/robbertkl/docker-ipv6nat&#34;&gt;docker-ipv6nat&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;IPv4 &amp;amp; IPv6 可以平等使用（端口可以在主机系统上共享）&lt;/li&gt;
&lt;li&gt;容器并不完全在线，因为 Docker 容器并不总是以安全着称&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;步骤1：&lt;/p&gt;
&lt;p&gt;为 ip6tables NAT 安装内核模块。不幸的是，QNAP 没有自带这些模块，所以你必须自己构建它们。谢天谢地，有人已经这样做并在 github 上发布了它。&lt;a href=&#34;https://github.com/mammo0/qnap-ip6tables_nat-module&#34;&gt;qnap-ip6tables_nat-module&lt;/a&gt;。在 Release 下，您已经可以在此处下载当前构建的模块。我将它们放在 Docker 容器的应用程序目录中：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;&amp;#x2F;share&amp;#x2F;CACHEDEV1_DATA&amp;#x2F;Container&amp;#x2F;container-station-data&amp;#x2F;application&amp;#x2F;ipv6nat&amp;#x2F;kernel_mods&amp;#x2F;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;然后，您必须确保在启动时加载这些模块。为此，必须在您的 &lt;a href=&#34;https://wiki.qnap.com/wiki/Running_Your_Own_Application_at_Startup&#34;&gt;autorun.sh&lt;/a&gt; 中输入以下行。&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;# ipv6-tables
# some required modules are already built into QTS, so you can load them with &amp;#39;modprobe&amp;#39;
&amp;#x2F;sbin&amp;#x2F;modprobe ip6_tables
&amp;#x2F;sbin&amp;#x2F;modprobe nf_nat
&amp;#x2F;sbin&amp;#x2F;modprobe xt_MASQUERADE

# then load the new module
insmod &amp;#x2F;share&amp;#x2F;CACHEDEV1_DATA&amp;#x2F;Container&amp;#x2F;container-station-data&amp;#x2F;application&amp;#x2F;ipv6nat&amp;#x2F;kernel_mods&amp;#x2F;ip6table_nat.ko&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;您可以在 QNAP Wiki 中了解如何在您的 QNAP 模型上编辑此文件：&lt;a href=&#34;https://wiki.qnap.com/wiki/Running_Your_Own_Application_at_Startup&#34;&gt;Running_Your_Own_Application_at_Startup&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;添加模块后，您需要重新启动 NAS。&lt;/p&gt;
&lt;p&gt;第2步：&lt;/p&gt;
&lt;p&gt;为了设置 docker-ipv6nat 容器，我准备了一个 docker-compose 文件。您可以通过 Create 简单地将其插入 ContainerStation：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-yaml&#34; data-language=&#34;yaml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;version: &amp;#39;3&amp;#39;

services:
  ipv6nat:
    container_name: ipv6nat
    restart: always
    image: robbertkl&amp;#x2F;ipv6nat
    privileged: true
    network_mode: host
    volumes:
      - &amp;#x2F;var&amp;#x2F;run&amp;#x2F;docker.sock:&amp;#x2F;var&amp;#x2F;run&amp;#x2F;docker.sock:ro
      - &amp;#x2F;lib&amp;#x2F;modules:&amp;#x2F;lib&amp;#x2F;modules:ro&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;容器应该在终端上没有任何输出的情况下启动。不起眼……现在呢？创建也应该可以通过 IPv&amp;amp; 访问的容器时是否需要一些手动工作。至少我还没有通过 QNAP 界面找到更简单的方法。&lt;/p&gt;
&lt;p&gt;如果需要，您必须为每个容器创建至少一个支持 IPv6 的网络。&lt;/p&gt;
&lt;p&gt;为此，请通过 SSH 登录 QNAP 并创建一个新的 Docker 网络：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker network create --ipv6 --subnet fd00:dead:beef::&amp;#x2F;48 ipv6net-1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;当然，您也可以使用任何其他 ULA 范围 (fc00::/7)。&lt;/p&gt;
&lt;p&gt;现在只需使用 ipv6net-1 作为容器的外部网络。这是一个小例子：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-yaml&#34; data-language=&#34;yaml&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;version: &amp;quot;3&amp;quot;
  services:
    alp1:
      image: yeasy&amp;#x2F;simple-web:latest
      ports:
      - 80:80
      networks:
      - ipv6net-1

networks:
  ipv6net-1:
    external: true&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;现在您的容器端口来自 IPv4：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;nmap &amp;lt;ipv4 ip&amp;gt; -p 80

PORT   STATE  SERVICE
80&amp;#x2F;tcp open http&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;也可以通过 IPv6 访问：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;nmap &amp;lt;ipv6 ip&amp;gt; -6 -p 80

PORT   STATE  SERVICE
80&amp;#x2F;tcp open http&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;享受通过 IPv4 和 IPv6 托管您的服务的乐趣！&lt;/p&gt;
&lt;h2 id=&#34;实操&#34;&gt;实操&lt;/h2&gt;
&lt;p&gt;遇到问题： robbertkl/ipv6nat 启动日志报错&lt;/p&gt;
&lt;p&gt;开启防火墙使用到了geoip会遇到如下错误&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-none&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;iptables   exit status 1: Can&amp;#39;t find library for match &amp;#96;geoip&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;可能的解决方案&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;编译支持 geoip&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gist.github.com/netrunn3r/d5d9eddde86a7ad7cd31a7d8e5d747c4&#34;&gt;Install geoip for iptables in Debian 10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.howtoforge.com/xtables-addons-on-centos-6-and-iptables-geoip-filtering&#34;&gt;Xtables-Addons On Centos 6 &amp;amp; Iptables GeoIP Filtering&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;
&lt;p&gt;不使用 geoip&lt;/p&gt;
&lt;p&gt;把防火墙中的规则设计到geoip 的都修改为任何地区，防火墙规则设计的好，针对一个地区开发某些端口和针对所有地区开发端口基本一样的风险。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;使用第二种方案，实际测试使用发现丢包率还不低！&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;# 在开启了ipv6的docker中运行如下命令
# ping6  2409:804c:2000:2::1
PING 2409:804c:2000:2::1 (2409:804c:2000:2::1): 56 data bytes
ping: getnameinfo: Temporary failure in name resolution
64 bytes from unknown: icmp_seq&amp;#x3D;1 ttl&amp;#x3D;57 time&amp;#x3D;4.073 ms

...

^Cping: getnameinfo: Temporary failure in name resolution
64 bytes from unknown: icmp_seq&amp;#x3D;24 ttl&amp;#x3D;57 time&amp;#x3D;3.654 ms
--- 2409:804c:2000:2::1 ping statistics ---
25 packets transmitted, 22 packets received, 12% packet loss
round-trip min&amp;#x2F;avg&amp;#x2F;max&amp;#x2F;stddev &amp;#x3D; 3.606&amp;#x2F;4.100&amp;#x2F;5.562&amp;#x2F;0.608 ms&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;Temporary failure in name resolution 似乎是DNS配置问题&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;测试IPV6&#34;&gt;测试IPV6&lt;/h2&gt;
&lt;h3 id=&#34;Windows&#34;&gt;Windows&lt;/h3&gt;
&lt;p&gt;以下Windows版本的&lt;code&gt;ping&lt;/code&gt;命令支持ping IPv6地址：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows XP with SP1 及以上&lt;/li&gt;
&lt;li&gt;Windows Vista 及以上&lt;/li&gt;
&lt;li&gt;Windows Server 2003 及以上&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;ping-ipv6主机名&#34;&gt;ping ipv6主机名&lt;/h4&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;ping -6 ipv6.google.com
ping -6 ipv6.test-ipv6.com  
ping -6 ipv6.baidu.com&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;**/!\注意：**当ping ipv6主机名时，必须加上参数&lt;code&gt;-6&lt;/code&gt;；直接ping IPv6地址时可以省略。&lt;/p&gt;
&lt;h4 id=&#34;ping-ipv6地址&#34;&gt;ping ipv6地址&lt;/h4&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;ping IPv6Address[%ZoneID]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;ping 2001:4860:0:2001::68&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;如果要ping link-local地址，则需要指定&lt;strong&gt;网络接口索引&lt;/strong&gt;，如：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;ping fe80::260:97ff:fe02:6ea5%4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;其中**%4**表示“用索引为4的网络接口”ping目标计算机。&lt;/p&gt;
&lt;h3 id=&#34;Linux&#34;&gt;Linux&lt;/h3&gt;
&lt;p&gt;在Linux发行版中，使用&lt;code&gt;ping6&lt;/code&gt;命令ping IPv6主机或者地址。&lt;/p&gt;
&lt;h4 id=&#34;ping-ipv6主机名-2&#34;&gt;ping ipv6主机名&lt;/h4&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;ping6 ipv6.google.com&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h4 id=&#34;ping-ipv6地址-2&#34;&gt;ping ipv6地址&lt;/h4&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;ping6 IPv6Address[%InterfaceName]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;如果要ping link-local地址，则需要指定&lt;strong&gt;网络接口名称&lt;/strong&gt;，如：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;ping fe80::260:97ff:fe02:6ea5%eth0&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;其中**%eth0**表示“用网络接口eth0 ping目标计算机”。&lt;/p&gt;
&lt;h4 id=&#34;ping-dns&#34;&gt;ping dns&lt;/h4&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;ping6  2409:804c:2000:2::1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h4 id=&#34;ssh&#34;&gt;ssh&lt;/h4&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;ssh root@fe80::c09a:4363:5763:32%enpxxx(网卡名称)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;chrome-IPV6&#34;&gt;chrome IPV6&lt;/h3&gt;
&lt;p&gt;Chrome 地址栏输入&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-none&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;about:net-internals&amp;#x2F;#dns&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;访问 &lt;a href=&#34;https://www.test-ipv6.com/&#34;&gt;https://www.test-ipv6.com/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;nginx支持ipv6&#34;&gt;nginx支持ipv6&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;nginx 1.14 开始就默认支持ipv6了，不再需要添加编译参数 --with-ipv6，可以直接配置监听 ipv6&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;检查nginx是否监听了ipv6&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-none&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;netstat -tuln&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;同时监听IPV4和IPV6&#34;&gt;同时监听IPV4和IPV6&lt;/h3&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-nginx&#34; data-language=&#34;nginx&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-nginx&#34;&gt;server  &amp;#123;
....
listen  [::]:80;
listen  [::]:443;
...
&amp;#125;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;只监听IPV6&#34;&gt;只监听IPV6&lt;/h3&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-nginx&#34; data-language=&#34;nginx&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-nginx&#34;&gt;server  &amp;#123;
....
listen  [::]:80  default  ipv6only&amp;#x3D;on;
listen  [::]:443  default  ipv6only&amp;#x3D;on;
...
&amp;#125;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;监听指定IPV6地址&#34;&gt;监听指定IPV6地址&lt;/h3&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-nginx&#34; data-language=&#34;nginx&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-nginx&#34;&gt;&amp;#123;
....
listen  [3608:f0f0:3002:31::1]:80;
listen  [3608:f0f0:3002:31::1]:443;
...
&amp;#125;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;重启nginx&#34;&gt;重启nginx&lt;/h3&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;nginx -s reload&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h2 id=&#34;安全设置&#34;&gt;安全设置&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;暴露Nas到公网是会有很大安全隐患的，请注意你已经做好了安全防范！&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;开启动态安全码&lt;/li&gt;
&lt;li&gt;开启防火墙，最好最高安全级别，自己控制端口开启&lt;/li&gt;
&lt;li&gt;开启IP访问保护 失败登录尝试阻止时间调整&lt;/li&gt;
&lt;li&gt;暴露出去的服务全部采用高强密码，最好所有服务全部采用高强度密码&lt;/li&gt;
&lt;li&gt;尽量使用Docker，而不是套件版软件服务&lt;/li&gt;
&lt;li&gt;重要数据定期离线备份&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;IPv6-为什么Link-local地址后面要有百分号-？&#34;&gt;IPv6: 为什么Link-local地址后面要有百分号(%)？&lt;/h2&gt;
&lt;p&gt;由于所有的link-local地址都有相同的前缀&lt;strong&gt;FE80::/64&lt;/strong&gt;，并且每个网络接口都必须分配一个link-local地址，因而导致当发送数据包到一个link-local地址时，如果路由器使用普通的路由方法就无法决定选用哪个网络接口。因此，引入了一种被叫做&lt;strong&gt;zone index&lt;/strong&gt;的标识符，它提供额外的路由信息，这个标识符通常指&lt;strong&gt;网络接口&lt;/strong&gt;，并且通过一个百分号(%)被附加在IPv6地址后面。但是准确的表示方法还取决于操作系统：&lt;/p&gt;
&lt;p&gt;Windows: 使用网络接口索引表示&lt;/p&gt;
&lt;p&gt;如：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;fe80::3%1
fe80::260:97ff:fe02:6ea5%4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;要查看网络接口索引，请执行该命令：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;netsh interface ipv6 show address&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Linux: 使用网络接口名称表示&lt;/p&gt;
&lt;p&gt;如：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;fe80::3%eth0
fe80::260:97ff:fe02:6ea5%tun0&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Linux只需要&lt;code&gt;ifconnfig&lt;/code&gt;命令就可列出所有网络接口名称。&lt;/p&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://geeks-r-us.de/2022/01/14/qnap-docker-ipv6-desater/&#34;&gt;qnap-docker-ipv6-desater/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.docker.com/config/daemon/ipv6/&#34;&gt;Enable IPv6 support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lesca.me/archives/how-to-ping-ipv6-address.html&#34;&gt;IPv6: 如何正确ping ipv6地址&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ipw.cn/doc/ipv6/user/ipv6_dns.html&#34;&gt;ipv6_dns&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;系列教程&#34;&gt;&lt;strong&gt;系列教程&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;全部文章RSS订阅&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;Docker系列&#34;&gt;&lt;strong&gt;Docker系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/docker/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;Docker 分类 RSS 订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/42b6a86d/&#34;&gt;Docker使用简明教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/9912bd5d/&#34;&gt;使用jeckett,sonarr,iyuu,qt,emby打造全自动追剧流程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/1802a8a7/&#34;&gt;为知笔记私有化Docker部署&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/593cc323/&#34;&gt;Earthly 一个更加强大的镜像构建工具&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/90e60aac/&#34;&gt;使用 Shell 脚本实现一个简单 Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/465d2738/&#34;&gt;如何使用Traefik V2 在Ubuntu20.04 上面来做 Dockers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/462f1e5c/&#34;&gt;通过IPV6访问Qnap NAS中Docker的服务&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;附赠&#34;&gt;附赠&lt;/h2&gt;
&lt;h3 id=&#34;alpine-linux-使用国内镜像源进行加速&#34;&gt;alpine linux 使用国内镜像源进行加速&lt;/h3&gt;
&lt;p&gt;Alpine 的源文件为：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-ini&#34; data-language=&#34;ini&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-ini&#34;&gt;&amp;#x2F;etc&amp;#x2F;apk&amp;#x2F;repositories&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;这里面的默认配置例如：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;http:&amp;#x2F;&amp;#x2F;dl-cdn.alpinelinux.org&amp;#x2F;alpine&amp;#x2F;v3.11&amp;#x2F;main
http:&amp;#x2F;&amp;#x2F;dl-cdn.alpinelinux.org&amp;#x2F;alpine&amp;#x2F;v3.11&amp;#x2F;community&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;可以使用以下命令来进行源的切换（阿里云源）：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;sed -i &amp;#39;s&amp;#x2F;dl-cdn.alpinelinux.org&amp;#x2F;mirrors.aliyun.com&amp;#x2F;g&amp;#39; &amp;#x2F;etc&amp;#x2F;apk&amp;#x2F;repositories&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;中国科技大学的源：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;sed -i &amp;#39;s&amp;#x2F;dl-cdn.alpinelinux.org&amp;#x2F;mirrors.ustc.edu.cn&amp;#x2F;g&amp;#39; &amp;#x2F;etc&amp;#x2F;apk&amp;#x2F;repositories&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;清华源：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;sed -i &amp;#39;s&amp;#x2F;dl-cdn.alpinelinux.org&amp;#x2F;mirrors.tuna.tsinghua.edu.cn&amp;#x2F;g&amp;#39; &amp;#x2F;etc&amp;#x2F;apk&amp;#x2F;repositories&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;目前 Docker 官方已开始推荐使用 Alpine 替代之前的 Ubuntu 做为基础镜像环境。&lt;/p&gt;
&lt;p&gt;Alpine 使用 apk 来进行包管理。&lt;/p&gt;
&lt;p&gt;可以在 Docker file 中添加以下语句，来加速 apk 的包管理。&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-docker&#34; data-language=&#34;docker&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-docker&#34;&gt;...
RUN sed -i &amp;#39;s&amp;#x2F;dl-cdn.alpinelinux.org&amp;#x2F;mirrors.tuna.tsinghua.edu.cn&amp;#x2F;g&amp;#39; &amp;#x2F;etc&amp;#x2F;apk&amp;#x2F;repositories
RUN apk add --no-cache gcc musl-dev linux-headers
...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;注： sed 可依照脚本的指令来处理、编辑文本文件。 Sed 主要用来自动编辑一个或多个文件、简化对文件的反复操作、编写转换程序等。&lt;/p&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10/dist/style.css&#34;&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/d3@7&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-view@0.18.10&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10&#34;&gt;&lt;/script&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;/css/markmap.css&#34;&gt;

&lt;script src=&#34;/js/markmap.js&#34;&gt;&lt;/script&gt;
</content>
        <category term="qnap" />
        <category term="nas" />
        <category term="docker" />
        <category term="ipv6" />
        <updated>2021-10-12T05:42:55.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.17lai.site/posts/1802a8a7/</id>
        <title>为知笔记私有化Docker部署</title>
        <link rel="alternate" href="https://blog.17lai.site/posts/1802a8a7/"/>
        <content type="html">&lt;blockquote&gt;
&lt;p&gt;已经不建议使用！&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;格式私有，不支持导出数据到其它类型笔记软件。数据进入后，基本只能用为知笔记编辑了，导出图片和&lt;code&gt;PDF&lt;/code&gt;? &lt;code&gt;what?&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;收费吃相难看，VIP到期后，功能限制严重！&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不推荐任何不支持导入导出数据的笔记软件。推荐使用开放格式的 Joplin 软件&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;登陆NAS，打开套件中心，搜索docker，并安装。&lt;/p&gt;
&lt;p&gt;搜索wiznote，找到wiznote/wizserver，双击下载&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114640.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;在NAS中创建共享目录，用于存放笔记数据&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;启动File Station&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在docker目录下创建文件夹：&lt;/p&gt;
&lt;p&gt;wiz&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在wiz文件夹下创建文件夹：data&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114704.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;双击创建容器，启用资源限制，设置为内存限制4096MB，官方介绍说需要4G内存&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114710.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;高级设置，启动自动重新启动&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;卷设置，使用刚才我们创建的data目录进行配置，装载路径&lt;code&gt;/wiz/storage&lt;/code&gt;，&lt;code&gt;docker/wiz/config&lt;/code&gt;装载路径&lt;code&gt;/wiz/app/wizserver/config&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114724.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;网络设置不动，端口设置添加映射：8888映射80端口（8888可以随便设置，跟访问地址有关）&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114731.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;ol start=&#34;8&#34;&gt;
&lt;li&gt;
&lt;p&gt;设置环境变量&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114732.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;SEARCH&amp;#x3D;true TZ&amp;#x3D;Asia&amp;#x2F;Shanghai&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;直接应用，启动docker，然后就静静的等待吧，可以看看镜像的日志，看到这些基本上就差不多启动好了（最新的镜像在NAS上首次启动非常慢，本人等了一个多小时才完全启动完毕，在本地安装速度非常快）&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114738.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过 &lt;code&gt;http://NAS的IP:8888&lt;/code&gt;，进行访问，就可以看到已经启动完成&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114744.jpeg&#34; alt=&#34;为知笔记启动界面&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;默认管理员账号：admin@wiz.cn，密码：123456&lt;/p&gt;
&lt;p&gt;管理后台登陆地址：&lt;a href=&#34;http://xn--IP-im8ckc&#34;&gt;http://IP地址&lt;/a&gt;:端口/wapp/pages/admin&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;NAS开启SSH&#34;&gt;NAS开启SSH&lt;/h2&gt;
&lt;p&gt;首先在NAS上启动SSH&lt;/p&gt;
&lt;p&gt;登陆NAS，打开&lt;code&gt;控制面板-终端机和SNMP&lt;/code&gt;，在&lt;code&gt;启动SSH功能&lt;/code&gt;前打上勾&lt;/p&gt;
&lt;p&gt;打开命令行，输入&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;ssh NAS管理员账号@NAS的IP地址 ssh端口号默认是22&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;看到提示符，输入账号的密码，输入时不可见，输入完成按回车，看到命令行提示符变了，登陆成功。&lt;/p&gt;
&lt;h2 id=&#34;进入容器&#34;&gt;进入容器&lt;/h2&gt;
&lt;p&gt;在命令行中输入&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;sudo docker ps&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;可能提示输入密码，就输入NAS管理员的密码即可，显示列表，查看到如下列表，找到其中运行了为知笔记的一行&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114805.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;复制为知笔记的&lt;code&gt;CONTAINER ID&lt;/code&gt;，然后再输入如下命令并回车：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;sudo docker exec -it 粘贴刚复制好的ID号 &amp;#x2F;bin&amp;#x2F;bash&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;至此进入到容器中&lt;/p&gt;
&lt;h2 id=&#34;修改配置文件&#34;&gt;修改配置文件&lt;/h2&gt;
&lt;p&gt;输入如下命令打开配置文件进行编辑：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;vi &amp;#x2F;wiz&amp;#x2F;app&amp;#x2F;wizserver&amp;#x2F;config&amp;#x2F;default.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;vi命令的具体使用方法请自行百度，保存好后退出，重启容器生效。&lt;/p&gt;
&lt;p&gt;进入docker，修改文件/wiz/wizserver/app/config/default.json&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-json&#34; data-language=&#34;json&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-json&#34;&gt;&amp;quot;as&amp;quot;: &amp;#123;
     &amp;quot;admin&amp;quot;: [&amp;quot;admin@wiz.cn&amp;quot;],
     &amp;quot;share&amp;quot;: &amp;#123;
       &amp;quot;admin&amp;quot;: [&amp;quot;admin@wiz.cn&amp;quot;],
       &amp;quot;enableHttps&amp;quot;: false,
       &amp;quot;enableSubDomain&amp;quot;: false,
       &amp;quot;appShareUrl&amp;quot;: &amp;quot;127.0.0.1:5001&amp;quot;
     &amp;#125;,&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;其中&lt;code&gt;127.0.0.1:5001&lt;/code&gt;修改为自己的服务器访问地址，可以给docker做个端口映射（因为群晖NAS占用了5001端口），譬如映射8889端口到容器的5001端口，则设置为&lt;code&gt;xxx.xxx.xxx.xxx:8889&lt;/code&gt;，分享后的链接即为该链接。&lt;/p&gt;
&lt;p&gt;在NAS上可以用反向代理来解决二级域名的问题。&lt;/p&gt;
&lt;p&gt;分享功能需要用户绑定手机，并完成认证，在docker中登陆数据库，并修改数据&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-sql&#34; data-language=&#34;sql&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-sql&#34;&gt;mysql -u root -p&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;输入密码，密码在docker中&lt;code&gt;/wiz/wizserver/app/config/default.json&lt;/code&gt;中查看&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-json&#34; data-language=&#34;json&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-json&#34;&gt;&amp;quot;mysql&amp;quot;: &amp;#123;
  &amp;quot;as&amp;quot;: &amp;#123;
   &amp;quot;host&amp;quot;: &amp;quot;127.0.0.1&amp;quot;,
   &amp;quot;user&amp;quot;: &amp;quot;root&amp;quot;,
   &amp;quot;password&amp;quot;: &amp;quot;******************&amp;quot;,
   &amp;quot;database&amp;quot;: &amp;quot;wizasent&amp;quot;,
   &amp;quot;connectionLimit&amp;quot;: 50,
   &amp;quot;connectTimeout&amp;quot;: 60000,
   &amp;quot;aquireTimeout&amp;quot;: 60000,
   &amp;quot;waitForConnections&amp;quot;: true
  &amp;#125;,&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;其中&lt;code&gt;password&lt;/code&gt;就是密码，进入mysql控制台后，执行以下命令：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-sql&#34; data-language=&#34;sql&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-sql&#34;&gt;use wizasent;
update wiz_user set MOBILE&amp;#x3D;&amp;#39;你的手机号&amp;#39;, MOBILE_VERIFY&amp;#x3D;&amp;#39;1&amp;#39; where ID&amp;#x3D;&amp;#39;1&amp;#39;;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;web端登陆为知笔记，并修改默认账号后，修改后的账号无法登陆管理后台，需要做以下配置，修改文件&lt;code&gt;/wiz/wizserver/app/config/default.json&lt;/code&gt;，找到以下代码：&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-json&#34; data-language=&#34;json&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-json&#34;&gt;&amp;quot;as&amp;quot;: &amp;#123;
  &amp;quot;admin&amp;quot;: [&amp;quot;admin@wiz.cn&amp;quot;],
  &amp;quot;share&amp;quot;: &amp;#123;
    &amp;quot;admin&amp;quot;: [&amp;quot;admin@wiz.cn&amp;quot;],
    &amp;quot;enableHttps&amp;quot;: false,
    &amp;quot;enableSubDomain&amp;quot;: false,
    &amp;quot;appShareUrl&amp;quot;: &amp;quot;127.0.0.1:5001&amp;quot;
  &amp;#125;,&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;其中&lt;code&gt;admin@wiz.cn&lt;/code&gt;修改为修改后的账号。&lt;/p&gt;
&lt;p&gt;登陆NAS，打开&lt;code&gt;控制面板-应用程序门户-反向代理&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;点击新增，然后输入如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114813.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;实际测试来源协议选择https时只有网页端可以登陆，客户端无法登陆，暂时还是选择http为好，也可以网页端通过https登陆，客户端通过http登陆，配置两个不同的端口（记得要在路由上配置端口映射）。&lt;/p&gt;
&lt;p&gt;修改&lt;code&gt;default.json&lt;/code&gt;&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-json&#34; data-language=&#34;json&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-json&#34;&gt;&amp;#123;
  &amp;quot;debug&amp;quot;: true,
  &amp;quot;enableHttps&amp;quot;: true,
  &amp;quot;storage&amp;quot;: &amp;#123;
     &amp;quot;__comments&amp;quot;: &amp;quot;oss|local|s3|cos&amp;quot;,
     &amp;quot;use&amp;quot;: &amp;quot;local&amp;quot;,
     &amp;quot;oss&amp;quot;: &amp;#123;
       &amp;quot;bucket&amp;quot;: &amp;quot;data_root&amp;quot;,
       &amp;quot;region&amp;quot;: &amp;quot;test&amp;quot;,
       &amp;quot;accessKeyId&amp;quot;: &amp;quot;test&amp;quot;,
       &amp;quot;accessKeySecret&amp;quot;: &amp;quot;test&amp;quot;,
       &amp;quot;internal&amp;quot;: false
     &amp;#125;,&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;其中&lt;code&gt;enableHttps&lt;/code&gt;配置成&lt;code&gt;true&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;重启服务&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;cd &amp;#x2F;wiz&amp;#x2F;app&amp;#x2F;wizserver
pm2 restart all&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;删除&lt;code&gt;/wiz/storage/index/.search&lt;/code&gt;文件和&lt;code&gt;/wiz/storage/index/nodes&lt;/code&gt;目录&lt;/p&gt;
&lt;p&gt;重启容器&lt;/p&gt;
&lt;p&gt;链接数据库，执行下列SQL&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-sql&#34; data-language=&#34;sql&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-sql&#34;&gt;update wizksent.wiz_kb_stat set index_new_status&amp;#x3D;4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;进入容器，执行&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;cd &amp;#x2F;wiz&amp;#x2F;app&amp;#x2F;wizserver
pm2 start app.js --name&amp;#x3D;&amp;quot;index2&amp;quot;  -f -- -c 1 -i 1 -t 2 -s index
pm2 start app.js --name&amp;#x3D;&amp;quot;index2&amp;quot;  -f --  -s copy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;查看日志&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;pm2 logs index2&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;下载官方插件，并安装到Chrome中&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.wiz.cn/downloads-webclipperchrome.html&#34;&gt;http://www.wiz.cn/downloads-webclipperchrome.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在浏览器中输入&lt;code&gt;chrome://extensions/&lt;/code&gt;打开插件列表，开启开发者模式&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114820.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;看ID号，在浏览器中输入&lt;code&gt;chrome://inspect/#extensions&lt;/code&gt;在打开的列表中找到&lt;code&gt;WizClipper&lt;/code&gt;，点击&lt;code&gt;inspect&lt;/code&gt;，开启调试窗口。&lt;/p&gt;
&lt;p&gt;选择&lt;code&gt;Sources&lt;/code&gt;标签，并打开文件&lt;code&gt;Scripts\wiz\WizConstant.js&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在代码中查看&lt;code&gt;note.wiz.cn&lt;/code&gt;和&lt;code&gt;api.wiz.cn&lt;/code&gt;的网址全部替换成自己私有云的地址，实测，登陆没问题，保存失败。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cimg1.17lai.site/data/2021/09/0920210909114826.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;管理功能&#34;&gt;管理功能&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;增加重建索引功能，以备不时之需&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;增加备份与恢复功能&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;增加markdown语法扩展：&lt;/p&gt;
&lt;p&gt;flow（流程图）、sequence（时序图）、mermaid（流程图、时序图、甘特图）、LaTeX（公式）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;增加手动配置分享链接&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持社交绑定的配置&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持对象存储或webdav存储&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;Web-Mac客户端&#34;&gt;Web&amp;amp;Mac客户端&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;增加自定义模板&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;增加偏好设置，自定义快捷键（主要是编辑和预览切换的快捷键非常不适应）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;增加同步预览模式，可以参考下Typora，Bear都不错&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;增加https访问方式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持导出jpg、png、docx格式&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;浏览器插件&#34;&gt;浏览器插件&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;增加支持私有云登陆&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;【部署环境】&lt;br&gt;
群晖DS1517+（DSM6.2.2）&lt;br&gt;
容器分配内存4G，CPU*2核&lt;/p&gt;
&lt;p&gt;【出现的问题】&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;网页版上提示，自动保存失败，网络错误，请尽快保存（最后发现是时区不通道导致的，第8点解决了此问题）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;所有社交平台账号无法绑定&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;mywiz邮箱不可修改&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;绑定手机无法收到验证码，即无法绑定手机（通过修改数据库搞定）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储设置功能多余（因为已经本地化部署了），改成数据备份/恢复就好了&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支付信息是支付到为知去的，这个功能容易产生误解（如果多人使用的话）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;docker容器的时区与宿主机时区不同，添加环境变量解决，TZ=Asia/Shanghai&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;数据导出&#34;&gt;数据导出&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;方法很困难。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;ExportToMd&#34;&gt;&lt;a href=&#34;https://github.com/lzuliuyun/ExportToMd&#34;&gt;ExportToMd&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;最新版Wiznote测试这个插件已经不可用了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;WizNotePlus&#34;&gt;&lt;strong&gt;&lt;a href=&#34;https://github.com/altairwei/WizNotePlus&#34;&gt;WizNotePlus&lt;/a&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;第三方客户端导出，已在 &lt;code&gt;v2.11&lt;/code&gt; 中初步实现，见如下&lt;code&gt;issue&lt;/code&gt;链接。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/altairwei/WizNotePlus/issues/182&#34;&gt;https://github.com/altairwei/WizNotePlus/issues/182&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;Memocast客户端&#34;&gt;Memocast客户端&lt;/h3&gt;
&lt;p&gt;随后搜索到&lt;a href=&#34;https://github.com/TankNee/Memocast&#34;&gt;Memocast&lt;/a&gt;，是重写为知笔记的客户端。&lt;/p&gt;
&lt;h3 id=&#34;使用OpenAPI&#34;&gt;使用OpenAPI&lt;/h3&gt;
&lt;p&gt;为知笔记提供了&lt;code&gt;OpenAPI&lt;/code&gt;来查看和编辑笔记，Memocast也是类似方式，&lt;a href=&#34;https://www.wiz.cn/wapp/pages/book/bb8f0f10-48ca-11ea-b27a-ef51fb9d4bb4/700c0ba0-48cb-11ea-a61a-d3d58d67def9&#34;&gt;服务说明及登录&lt;/a&gt;和&lt;a href=&#34;https://www.wiz.cn/wapp/pages/book/bb8f0f10-48ca-11ea-b27a-ef51fb9d4bb4/475c9ef0-4e1a-11ea-8f5c-a7618da01da2&#34;&gt;笔记接口&lt;/a&gt;介绍了如何登录获取Token，如何查询文件夹文档，下载html，下载资源(图片)等接口。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对于为知笔记来说，所有的笔记保存为html，所以下载后需要做转换。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;通过&lt;code&gt;Postman&lt;/code&gt;的接口测试发现完全可行，那么就可以编程导出了。具体的&lt;a href=&#34;https://github.com/GalaIO/wiz_export&#34;&gt;代码&lt;/a&gt;。html转md的库使用&lt;a href=&#34;https://github.com/JohannesKaufmann/html-to-markdown&#34;&gt;html-to-markdown&lt;/a&gt;，案例代码&lt;a href=&#34;https://github.com/JohannesKaufmann/html-to-markdown/blob/master/examples/github_flavored/main.go&#34;&gt;在这&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;如何使用？按照下面提示输入账户和密码，然后设置导出的文件夹即可。&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;go install github.com&amp;#x2F;GalaIO&amp;#x2F;wiz_export@latest
wiz_export --output &amp;#39;.&amp;#x2F;&amp;#39; --userId &amp;#39;xx&amp;#39; --password &amp;#39;xx&amp;#39; --folders &amp;#39;&amp;#x2F;日记&amp;#x2F;,&amp;#x2F;工作&amp;#x2F;&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;借助Pandoc转换到HTML再转换到Markdown&#34;&gt;借助Pandoc转换到HTML再转换到Markdown&lt;/h3&gt;
&lt;p&gt;For .html files within a directory&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-none&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;for f in *.html ; do pandoc $&amp;#123;f&amp;#125; -f html -t markdown -s -o $&amp;#123;f&amp;#125;.md ; done&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;p&gt;For recursive directory conversion with subfolders&lt;/p&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-none&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;find . -name &amp;quot;*.ht*&amp;quot; | while read i; do pandoc -f html -t markdown &amp;quot;$i&amp;quot; -o &amp;quot;$&amp;#123;i%.*&amp;#125;.md&amp;quot;; done&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;wiz2joplin&#34;&gt;&lt;a href=&#34;https://github.com/zrong/wiz2joplin&#34;&gt;wiz2joplin&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这个需要Mac环境，看提交记录时间，可行性很高。&lt;/p&gt;
&lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://mp.weixin.qq.com/s/JQBUqdq1YNsGqolQ0jjfNg&#34;&gt;大大木头 [为知社区]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://zhuanlan.zhihu.com/p/488613809&#34;&gt;如何导出为知笔记？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://discourse.joplinapp.org/t/importing-notes-from-other-notebook-applications/22425&#34;&gt;Importing notes from other notebook applications&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;系列教程&#34;&gt;&lt;strong&gt;系列教程&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;全部文章RSS订阅&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;Docker系列&#34;&gt;&lt;strong&gt;Docker系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/docker/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;Docker 分类 RSS 订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/42b6a86d/&#34;&gt;Docker使用简明教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/9912bd5d/&#34;&gt;使用jeckett,sonarr,iyuu,qt,emby打造全自动追剧流程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/1802a8a7/&#34;&gt;为知笔记私有化Docker部署&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/593cc323/&#34;&gt;Earthly 一个更加强大的镜像构建工具&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/90e60aac/&#34;&gt;使用 Shell 脚本实现一个简单 Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/465d2738/&#34;&gt;如何使用Traefik V2 在Ubuntu20.04 上面来做 Dockers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/462f1e5c/&#34;&gt;通过IPV6访问Qnap NAS中Docker的服务&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;笔记系列&#34;&gt;&lt;strong&gt;笔记系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/note/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;Note分类RSS订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/a8535f26/&#34;&gt;完美笔记进化论&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/253706ff/&#34;&gt;hexo博客博文撰写篇之完美笔记大攻略终极完全版&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/e6086437/&#34;&gt;Joplin入门指南&amp;amp;实践方案&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/45f878cd/&#34;&gt;替代Evernote免费开源笔记Joplin-网盘同步笔记历史版本Markdown可视化&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/92d347d6/&#34;&gt;Joplin 插件以及其Markdown语法。All in One!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/e3ee7f8b/&#34;&gt;Joplin 插件使用推荐&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/1802a8a7/&#34;&gt;为知笔记私有化Docker部署&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;Gitbook使用系列&#34;&gt;&lt;strong&gt;Gitbook使用系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/gitbook/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;Gitbook分类RSS订阅&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/7fe86002/&#34;&gt;GitBook+GitLab撰写发布技术文档-Part1:GitBook篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/7790e989/&#34;&gt;GitBook+GitLab撰写发布技术文档-Part2:GitLab篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/d6bad1e5/&#34;&gt;自己动手制作电子书的最佳方式（支持PDF、ePub、mobi等格式）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;Gitlab-使用系列&#34;&gt;&lt;strong&gt;Gitlab 使用系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/gitlab/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;Gitlab RSS 分类订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/acc13b70/&#34;&gt;&lt;strong&gt;Gitlab的安装及使用教程完全版&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/29a820b3/&#34;&gt;破解Gitlab EE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/d08eb7b/&#34;&gt;Gitlab的安装及使用&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/1879721e/&#34;&gt;CI/CD与Git Flow与GitLab&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;Hexo系列&#34;&gt;&lt;strong&gt;Hexo系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/hexo/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;HexoRSS分类订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[十万字图文教程]基于Hexo的matery主题搭建博客并深度优化完全一站式教程&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/40300608/&#34;&gt;Hexo Docker环境与Hexo基础配置篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/4d8a0b22/&#34;&gt;hexo博客自定义修改篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/9b056c86/&#34;&gt;hexo博客网络优化篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/5311b619/&#34;&gt;hexo博客增强部署篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/4a2050e2/&#34;&gt;hexo博客个性定制篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/84b4059a/&#34;&gt;hexo博客常见问题篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/253706ff/&#34;&gt;hexo博客博文撰写篇之完美笔记大攻略终极完全版&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/cf0f47fd/&#34;&gt;Hexo Markdown以及各种插件功能测试&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;markdown 各种其它语法插件，latex公式支持，mermaid图表，plant uml图表，URL卡片，bilibili卡片，github卡片，豆瓣卡片，插入音乐和视频，插入脑图，插入PDF，嵌入iframe&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/217ccdc1/&#34;&gt;在 Hexo 博客中插入 ECharts 动态图表&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/546887ac/&#34;&gt;使用nodeppt给hexo博客嵌入PPT演示&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/a3c81cc3/&#34;&gt;GithubProfile美化与自动获取RSS文章教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/e922fac8/&#34;&gt;Vercel部署高级用法教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/eb731135/&#34;&gt;webhook部署Hexo静态博客指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/8f9792ab/&#34;&gt;在宝塔VPS上面采用docker部署waline全流程图解教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/843eb2k9/&#34;&gt;自建Umami访问统计服务并统计静态博客UV/PV&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10/dist/style.css&#34;&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/d3@7&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-view@0.18.10&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10&#34;&gt;&lt;/script&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;/css/markmap.css&#34;&gt;

&lt;script src=&#34;/js/markmap.js&#34;&gt;&lt;/script&gt;
</content>
        <category term="linux" />
        <category term="nas" />
        <category term="wiz" />
        <category term="docker" />
        <category term="note" />
        <updated>2021-09-09T01:25:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.17lai.site/posts/42b6a86d/</id>
        <title>docker使用简明教程</title>
        <link rel="alternate" href="https://blog.17lai.site/posts/42b6a86d/"/>
        <content type="html">&lt;p&gt;关于docker安装，查看，镜像管理，以及一个实用Dockerfile， LAMP，PHP，LTMJ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;关于本blog，&lt;strong&gt;图床&lt;/strong&gt;一般使用&lt;strong&gt;github&lt;/strong&gt;，已经配置了CDN，如果图片还是未显示请自行代理解决&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;Docker安装&#34;&gt;Docker安装&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在Ubuntu系统下安装：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;apt-get install docker&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;在Fedora/CentOS系统下安装：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;yum install docker
dnf install docker # Fedora 25+&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;在SUSE系统下安装：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;zypper install docker&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h2 id=&#34;Docker容器&#34;&gt;Docker容器&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;首先启动Docker&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;# 启动Docker
systemctl start docker
# 设置开机自启动，可选
systemctl enable docker&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;启动Docker测试容器&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker run &amp;quot;hello-world&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;在启动容器时，如果使用的镜像在本地不存在，会尝试从网络上获取。&lt;/li&gt;
&lt;li&gt;在一般情况下，启动Web服务的容器，使用以下命令：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;# -d：daemon，使容器在后台运行
# -p：port，指定容器的端口，这里是将容器的80端口映射到主机的8001端口
docker run -d -p 8001:80 &amp;quot;image_name&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;查看容器运行情况&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker ps&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;Docker会为容器分配一个Container ID和一个Container Name，Name可以在运行时通过&lt;code&gt;-name&lt;/code&gt;自行指定，这两个可以用来标识容器。&lt;/li&gt;
&lt;li&gt;需要停止容器时，使用以下命令：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker stop &amp;quot;container_name&amp;quot;
# 或使用ID查找
docker stop &amp;quot;container_id&amp;quot;
# 重启
docker restart &amp;quot;container_id&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h2 id=&#34;Docker镜像&#34;&gt;Docker镜像&lt;/h2&gt;
&lt;h3 id=&#34;Dokerfile编译镜像&#34;&gt;Dokerfile编译镜像&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Docker容器是运行的Docker镜像实例，一般情况下，我们需要制作自己的Docker镜像。&lt;/li&gt;
&lt;li&gt;Docker镜像的制作依赖于Dockerfile，我们稍后在讨论Dockerfile的编写，这里假定我们有一个编写好的Dockerfile。&lt;/li&gt;
&lt;li&gt;下面的命令将在当前路径查找Dockerfile并构建一个名为“image_name”的镜像。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker build -t &amp;quot;image_name&amp;quot; .&amp;#x2F;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;查看本地所有镜像&#34;&gt;查看本地所有镜像&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在构建过程中需要在网络上下载来源镜像，可能需要一段时间。&lt;/li&gt;
&lt;li&gt;如果Dockerfile中的命令都正确结束（Exit code 0），那么Docker镜像的构建也将顺利完成，我们可以通过下面的命令查看我们的所有镜像：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker images&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;导出备份已有镜像文件&#34;&gt;导出备份已有镜像文件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;我们还可以导出我们制作好的Docker镜像，下面的命令将image_name镜像导出为image_name.tar&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker save &amp;quot;image_name&amp;quot; &amp;gt; image_name.tar&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;导入已有镜像备份&#34;&gt;导入已有镜像备份&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在另一台机器上，我们不需要网络就可以导入并使用该镜像：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;docker load &amp;lt; image_name.tar&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h2 id=&#34;Dockerfile&#34;&gt;Dockerfile&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Dockerfile本质上是一组命令集合，用于自动化构建镜像，下面以几个实例来说明Dockerfile的编写方法：&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;实例一：LAMP（Linux-Apache-MySQL-PHP）环境配置&#34;&gt;实例一：LAMP（Linux+Apache+MySQL+PHP）环境配置&lt;/h3&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-docker&#34; data-language=&#34;docker&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-docker&#34;&gt;# 来源镜像，一般可以使用标准的系统或者带有各种环境的系统
# 显然这里使用的是标准的Ubuntu 14.04系统
FROM ubuntu:14.04
# 镜像作者
MAINTAINER wrlu
# 刷新日期
ENV REFRESHED_AT 2018-08-05
# 设定字符集
ENV LANG C.UTF-8
# RUN命令用于执行系统命令
# 因为需要自动化安装，所以最好通过-y命令跳过确认
# 更新APT软件源
RUN apt-get update -y
# 安装MySQL
RUN apt-get -y install mysql-server
# 安装Apache
RUN apt-get -y install apache2
# 安装PHP5
RUN apt-get -y install php5 libapache2-mod-php5
RUN apt-get install -yqq php5-mysql php5-curl php5-gd php5-intl php-pear php5-imagick php5-imap php5-mcrypt php5-memcache php5-ming php5-ps php5-pspell php5-recode php5-snmp php5-sqlite php5-tidy php5-xmlrpc php5-xsl
# 删除Apache2列出目录配置
RUN sed -i &amp;#39;s&amp;#x2F;Options Indexes FollowSymLinks&amp;#x2F;Options None&amp;#x2F;&amp;#39; &amp;#x2F;etc&amp;#x2F;apache2&amp;#x2F;apache2.conf
# COPY命令可以复制文件，但是似乎不能递归复制文件
COPY IncludeAirline&amp;#x2F;* &amp;#x2F;var&amp;#x2F;www&amp;#x2F;html&amp;#x2F;
COPY IncludeAirline&amp;#x2F;airlines&amp;#x2F;* &amp;#x2F;var&amp;#x2F;www&amp;#x2F;html&amp;#x2F;airlines&amp;#x2F;
# 删除默认的主页
RUN rm &amp;#x2F;var&amp;#x2F;www&amp;#x2F;html&amp;#x2F;index.html
# 复制启动脚本
COPY start.sh &amp;#x2F;root&amp;#x2F;start.sh
RUN chmod +x &amp;#x2F;root&amp;#x2F;start.sh
# 设置启动目录以及启动脚本
ENTRYPOINT cd &amp;#x2F;root; .&amp;#x2F;start.sh
# 设置需要暴露的端口
EXPOSE 80,3306 &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;本例中还有一个启动脚本&lt;code&gt;start.sh&lt;/code&gt;，用于导入数据库，编写如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;#!&amp;#x2F;bin&amp;#x2F;bash
# 启动后延时
sleep 1
# 启动Apache服务器
&amp;#x2F;etc&amp;#x2F;init.d&amp;#x2F;apache2 start
# 启动MySQL数据库
find &amp;#x2F;var&amp;#x2F;lib&amp;#x2F;mysql -type f -exec touch &amp;#123;&amp;#125; ; &amp;amp;&amp;amp; service mysql start
# 定义SQL文件路径
sqlfile&amp;#x3D;&amp;#x2F;var&amp;#x2F;www&amp;#x2F;html&amp;#x2F;includeAirline.sql
if [ -f $flagfile ]; then
	# 修改MySQL的密码
    mysqladmin -u root password &amp;quot;root&amp;quot;
    # 登录MySQL并导入SQL文件执行
    mysql -uroot -proot &amp;lt; $sqlfile
    # 删除SQL文件
    rm -f $sqlfile
fi
# 此处注意，如果命令执行完后脚本退出
# 则Docker容器也会因为没有前台应用运行而中止
# 所以这里使用一个前台命令来保活Docker容器
tail -f &amp;#x2F;var&amp;#x2F;log&amp;#x2F;apache2&amp;#x2F;error.log&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;实例二：PHP环境配置：&#34;&gt;实例二：PHP环境配置：&lt;/h3&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-docker&#34; data-language=&#34;docker&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-docker&#34;&gt;# 来源镜像，自带Apache+PHP环境
FROM php:7.0-apache
MAINTAINER tl
ENV REFRESHED_AT 2018‐08‐03
ENV LANG C.UTF‐8
# ADD命令在COPY命令的基础上，具有自动解包tar的功能
ADD web_tired.tar &amp;#x2F;var&amp;#x2F;www&amp;#x2F;html&amp;#x2F;
EXPOSE 80&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h3 id=&#34;实例三：LTMJ（Linux-Tomcat-MySQL-JSP）环境配置&#34;&gt;实例三：LTMJ（Linux+Tomcat+MySQL+JSP）环境配置&lt;/h3&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-docker&#34; data-language=&#34;docker&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-docker&#34;&gt;FROM ubuntu:16.04
MAINTAINER wrlu
ENV REFRESHED_AT 2018-08-05
ENV LANG C.UTF-8
RUN apt-get update -y
RUN apt-get -y install mysql-server
# 安装wget，因为Docker提供的镜像是最小镜像，所以用到的其他工具需要自行安装
RUN apt-get -y install wget
# 安装Java 8
RUN apt-get -y install openjdk-8-jre
# 下载Tomcat 8服务器
RUN wget http:&amp;#x2F;&amp;#x2F;mirrors.hust.edu.cn&amp;#x2F;apache&amp;#x2F;tomcat&amp;#x2F;tomcat-8&amp;#x2F;v8.5.32&amp;#x2F;bin&amp;#x2F;apache-tomcat-8.5.32.tar.gz
# 解压tar.gz
RUN tar -xzf apache-tomcat-8.5.32.tar.gz -C &amp;#x2F;root
RUN mv &amp;#x2F;root&amp;#x2F;apache-tomcat-8.5.32 &amp;#x2F;root&amp;#x2F;tomcat
# 删除默认页面
RUN rm -rf &amp;#x2F;root&amp;#x2F;tomcat&amp;#x2F;webapps&amp;#x2F;*
# 拷贝war文件
COPY CAAC-SQL-Injection.war &amp;#x2F;root&amp;#x2F;tomcat&amp;#x2F;webapps&amp;#x2F;
COPY wafwtf.sql &amp;#x2F;root&amp;#x2F;
COPY start.sh &amp;#x2F;root&amp;#x2F;start.sh
RUN chmod +x &amp;#x2F;root&amp;#x2F;start.sh
ENTRYPOINT cd &amp;#x2F;root; .&amp;#x2F;start.sh
# Tomcat使用8080端口，不同于Apache
EXPOSE 8080&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;启动脚本如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;div class=&#34;code-area&#34;&gt;&lt;pre class=&#34;line-numbers language-bash&#34; data-language=&#34;bash&#34; data-start=&#34;1&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;#!&amp;#x2F;bin&amp;#x2F;bash
sleep 1
find &amp;#x2F;var&amp;#x2F;lib&amp;#x2F;mysql -type f -exec touch &amp;#123;&amp;#125; ; &amp;amp;&amp;amp; service mysql start
chmod +x &amp;#x2F;root&amp;#x2F;tomcat&amp;#x2F;bin&amp;#x2F;startup.sh
# 启动Tomcat服务器
&amp;#x2F;root&amp;#x2F;tomcat&amp;#x2F;bin&amp;#x2F;startup.sh
sqlfile&amp;#x3D;&amp;#x2F;root&amp;#x2F;wafwtf.sql
if [ -f $flagfile ]; then
    mysqladmin -u root password &amp;quot;root&amp;quot;
    mysql -uroot -proot &amp;lt; $sqlfile
    rm -f $sqlfile
fi
# 容器保活
tail -f &amp;#x2F;root&amp;#x2F;tomcat&amp;#x2F;conf&amp;#x2F;server.xml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/figure&gt;
&lt;h2 id=&#34;系列教程&#34;&gt;&lt;strong&gt;系列教程&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;全部文章RSS订阅&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;Docker系列&#34;&gt;&lt;strong&gt;Docker系列&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;/categories/docker/atom.xml&#34;&gt;&lt;i class=&#34;fas fa-rss&#34;&gt;&lt;/i&gt;&lt;strong&gt;Docker 分类 RSS 订阅&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/posts/42b6a86d/&#34;&gt;Docker使用简明教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/9912bd5d/&#34;&gt;使用jeckett,sonarr,iyuu,qt,emby打造全自动追剧流程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/1802a8a7/&#34;&gt;为知笔记私有化Docker部署&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/593cc323/&#34;&gt;Earthly 一个更加强大的镜像构建工具&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/90e60aac/&#34;&gt;使用 Shell 脚本实现一个简单 Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/465d2738/&#34;&gt;如何使用Traefik V2 在Ubuntu20.04 上面来做 Dockers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;/posts/462f1e5c/&#34;&gt;通过IPV6访问Qnap NAS中Docker的服务&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10/dist/style.css&#34;&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/d3@7&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-view@0.18.10&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://fastly.jsdelivr.net/npm/markmap-toolbar@0.18.10&#34;&gt;&lt;/script&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;/css/markmap.css&#34;&gt;

&lt;script src=&#34;/js/markmap.js&#34;&gt;&lt;/script&gt;
</content>
        <category term="linux" />
        <category term="mysql" />
        <category term="docker" />
        <category term="lamp" />
        <category term="php" />
        <category term="tomcat" />
        <updated>2021-08-01T12:25:00.000Z</updated>
    </entry>
</feed>
