<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Virtualization</title>
	<atom:link href="https://blog.gmem.cc/category/work/cloud/virtualization/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Sun, 19 Apr 2026 07:54:29 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Kata Containers学习笔记</title>
		<link>https://blog.gmem.cc/kata-containers-study-note</link>
		<comments>https://blog.gmem.cc/kata-containers-study-note#comments</comments>
		<pubDate>Tue, 04 Dec 2018 06:55:40 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[PaaS]]></category>
		<category><![CDATA[Virtualization]]></category>
		<category><![CDATA[K8S]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=23919</guid>
		<description><![CDATA[<p>简介 Kata Containers是基于Intel Clear Container + Hyper runV实现的轻量级的虚拟机，能够无缝的集成到容器生态系统中。Kata和Container一样轻量、快速，同时具有传统虚拟化的安全优势，Kata和Docker的OCI规范、K8S的CRI接口兼容。 安全性 迁移到容器云需要面临的一个重要挑战就是安全问题。多租户环境下，不安全负载和受信任负载在一起运行。Kata利用基于硬件的隔离，实现容器/Pod的安全边界，解决了传统容器架构共享内核的安全缺陷（提权漏洞）。使用Kata，你可以在单个K8S集群中服务多个租户。 兼容性 支持工业标准，包括OCI容器格式、K8S的CRI接口。 遗留系统支持 这是虚拟化的优势，即允许使用任意的客户机操作系统，允许设备穿透访问。 架构 Kata包含Agent、Runtime、Proxy、Shim、Kernel、Hypervisor等组件。 kata-agent 运行在虚拟机内部，负责创建容器环境和进程。 kata-proxy 运行在宿主机上，负责协调和Agent的交互。 kata-ksm-throttler 可选的工具，用于监控容器，去除重复内存，以最大化容器部署密度。 <a class="read-more" href="https://blog.gmem.cc/kata-containers-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/kata-containers-study-note">Kata Containers学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Kata Containers是基于Intel Clear Container + Hyper runV实现的<span style="background-color: #c0c0c0;">轻量级的虚拟机</span>，能够无缝的集成到容器生态系统中。Kata和Container一样轻量、快速，同时具有传统虚拟化的安全优势，Kata和Docker的OCI规范、K8S的CRI接口兼容。</p>
<div class="blog_h2"><span class="graybg">安全性</span></div>
<p>迁移到容器云需要面临的一个重要挑战就是安全问题。多租户环境下，不安全负载和受信任负载在一起运行。Kata利用基于硬件的隔离，实现容器/Pod的安全边界，解决了传统容器架构共享内核的安全缺陷（提权漏洞）。使用Kata，你可以在单个K8S集群中服务多个租户。</p>
<div class="blog_h2"><span class="graybg">兼容性</span></div>
<p>支持工业标准，包括OCI容器格式、K8S的CRI接口。</p>
<div class="blog_h2"><span class="graybg">遗留系统支持</span></div>
<p>这是虚拟化的优势，即允许使用任意的客户机操作系统，允许设备穿透访问。</p>
<div class="blog_h2"><span class="graybg">架构</span></div>
<p>Kata包含Agent、Runtime、Proxy、Shim、Kernel、Hypervisor等组件。</p>
<div class="blog_h3"><span class="graybg">kata-agent</span></div>
<p>运行在虚拟机内部，负责创建容器环境和进程。</p>
<div class="blog_h3"><span class="graybg">kata-proxy </span></div>
<p>运行在宿主机上，负责协调和Agent的交互。</p>
<div class="blog_h3"><span class="graybg">kata-ksm-throttler </span></div>
<p>可选的工具，用于监控容器，去除重复内存，以最大化容器部署密度。</p>
<div class="blog_h3"><span class="graybg">kata-runtime</span></div>
<p>容器管理器调用此组件，该组件提供操控容器的高层接口，提供了CLI。该组件和OCI、CRI-O、Containerd兼容。</p>
<div class="blog_h3"><span class="graybg">kata-shim</span></div>
<p>运行在宿主机上的进程，为了兼容OCI规范而存在。垫片在宿主机上代表工作负载（运行在虚拟机）处理标准的IO、信号。</p>
<div class="blog_h3"><span class="graybg">Hypervisor</span></div>
<p>使用QEMU 2.11。</p>
<div class="blog_h3"><span class="graybg">Kernel</span></div>
<p>QEMU使用Linux内核来启动客户机镜像。 </p>
<div class="blog_h1"><span class="graybg">安装</span></div>
<div class="blog_h2"><span class="graybg">前置条件检查</span></div>
<p>宿主机必须支持KVM：</p>
<pre class="crayon-plain-tag">apt-get install ubuntu-virt-server

kvm-ok
# INFO: /dev/kvm exists
# KVM acceleration can be used</pre>
<p> 如果宿主机本身就是虚拟机，则宿主机的宿主机应当开启嵌套虚拟化：</p>
<pre class="crayon-plain-tag">cat /sys/module/kvm_intel/parameters/nested
# Y </pre>
<div class="blog_h2"><span class="graybg">安装Kata组件</span></div>
<pre class="crayon-plain-tag">echo "deb http://download.opensuse.org/repositories/home:/katacontainers:/release/xUbuntu_$(lsb_release -rs)/ /" &gt; /etc/apt/sources.list.d/kata-containers.list
curl -sL http://download.opensuse.org/repositories/home:/katacontainers:/release/xUbuntu_$(lsb_release -rs)/Release.key | sudo apt-key add -
apt update &amp;&amp; apt -y install kata-runtime kata-proxy kata-shim</pre>
<p>执行下面的命令，检查当前环境是否具备运行Kata的条件：</p>
<pre class="crayon-plain-tag">kata-runtime kata-check</pre>
<p>如果一切正常，应当没有错误日志。</p>
<div class="blog_h1"><span class="graybg">Docker集成</span></div>
<div class="blog_h2"><span class="graybg">添加Runtime</span></div>
<p>创建配置文件：</p>
<pre class="crayon-plain-tag">[Service]
Type=simple
ExecStart=
ExecStart=/usr/bin/dockerd -D --default-runtime runc --add-runtime kata-runtime=/usr/bin/kata-runtime</pre>
<p>重启Docker：</p>
<pre class="crayon-plain-tag">systemctl daemon-reload 
systemctl restart docker.service
docker info | grep runtime
# Runtimes: kata-runtime runc</pre>
<div class="blog_h2"><span class="graybg">测试</span></div>
<p>执行下面的命令，以Kata作为Runtime创建容器：</p>
<pre class="crayon-plain-tag">docker run -d --name ubuntu --runtime kata-runtime docker.gmem.cc/ubuntu:16.04 sleep 3600</pre>
<p>容器运行起来之后，你可以看到QEMU进程qemu-lite-system-x86_64。 </p>
<div class="blog_h1"><span class="graybg">K8S集成</span></div>
<p>Kata是OCI兼容的容器运行时，不能直接和K8S的CRI API交互。因此，想集成K8S，先要安装一个支持CRI - OCI适配的CRI实现，可供选择的有 CRI-O、 CRI-containerd。</p>
<div class="blog_h2"><span class="graybg">CRI-O方案</span></div>
<p>CRI-O是基于OCI的CRI实现。 </p>
<div class="blog_h3"><span class="graybg">安装skopeo</span></div>
<p>skopeo是一个命令行工具，支持对容器镜像、仓库进行各种操作。skopeo支持OCI镜像以及Docker v2镜像格式。skopeo可以和支持API v2的镜像仓库进行交互。</p>
<p>和Docker不同，skopeo不需要运行守护程序即可完成以下操作：</p>
<ol>
<li>在多种存储机制之间拷贝镜像，例如从一个仓库拷贝到另外一个</li>
<li>查看远程镜像的属性，包括它的层，而不需要预先拉取到本地</li>
<li>从仓库中删除镜像</li>
<li>支持向仓库提供身份凭证信息</li>
</ol>
<p>执行下面的命令安装：</p>
<pre class="crayon-plain-tag">add-apt-repository ppa:projectatomic/ppa
apt-get update
apt-get install skopeo-containers -y </pre>
<div class="blog_h3"><span class="graybg">安装runc</span></div>
<p>这是一个轻巧的CLI工具，可以基于OCI规范产生和运行容器进程。</p>
<pre class="crayon-plain-tag">pushd /usr/bin
wget https://github.com/opencontainers/runc/releases/download/v1.0.0-rc6/runc.amd64
mv runc.amd64 runc
chmod +x runc</pre>
<p>CRI-O可以支持多种运行时，可以将runc作为默认运行时，让kata作为不受信任负载的运行时。</p>
<div class="blog_h3"><span class="graybg">构建crio</span></div>
<p>crio没有提供预编译的二进制文件，你需要自己编译。首先安装Go的SDK并初始化工作区：</p>
<pre class="crayon-plain-tag">wget https://dl.google.com/go/go1.11.4.linux-amd64.tar.gz
tar zxf g1.11.4.linux-amd64.tar.gz
mv go $HOME/Go/sdk/1.11.4
mkdir -p $HOME/Go/workspaces/default
export GOROOT=$HOME/Go/sdk/1.11.4
export GOPATH=$HOME/Go/workspaces/default
export PATH=$PATH:$GOROOT/bin</pre>
<p>然后构建crictl：</p>
<pre class="crayon-plain-tag">go get github.com/kubernetes-incubator/cri-tools/cmd/crictl
cd $GOPATH/src/github.com/kubernetes-incubator/cri-tools
git checkout release-1.12
make
make install</pre>
<p>然后从源码构建crio： </p>
<pre class="crayon-plain-tag">apt install -y libglib2.0-dev libseccomp-dev  libgpgme11-dev  libdevmapper-dev make git gcc go-md2man

go get -d github.com/kubernetes-sigs/cri-o
cd $GOPATH/src/github.com/kubernetes-sigs/cri-o
git checkout release-1.12
make install.tools
make
make install

# 生成并安装配置文件
make install.config</pre>
<div class="blog_h3"><span class="graybg">配置crio</span></div>
<p>修改配置文件中的以下字段：</p>
<pre class="crayon-plain-tag">[crio.image]
registries = ['docker.io']

[crio.runtime]
manage_network_ns_lifecycle = true
# 不受信任的负载用Kata运行
runtime_untrusted_workload = "/usr/bin/kata-runtime"
# 默认认为负载是受信任的
default_workload_trust = "trusted"
log_level = "info"</pre>
<p>注意，CRI-O默认使用的运行时是runc。</p>
<p>创建Systemd服务定义：</p>
<pre class="crayon-plain-tag">Description=OCI-based implementation of Kubernetes Container Runtime Interface
Documentation=https://github.com/kubernetes-sigs/cri-o

[Service]

Environment="HTTP_PROXY=http://10.0.0.1:8087"
Environment="HTTPS_PROXY=http://10.0.0.1:8087"

ExecStart=/usr/local/bin/crio
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target</pre>
<div class="blog_h3"><span class="graybg">启动crio服务</span></div>
<pre class="crayon-plain-tag">systemctl daemon-reload
systemctl enable crio
systemctl start crio</pre>
<p>使用下面的命令检测crio是否正常运行：</p>
<pre class="crayon-plain-tag">crictl --runtime-endpoint unix:///var/run/crio/crio.sock version

# Version:  0.1.0
# RuntimeName:  cri-o
# RuntimeVersion:  1.14.0-dev
# RuntimeApiVersion:  v1alpha1</pre>
<div class="blog_h3"><span class="graybg">配置kubelet</span></div>
<pre class="crayon-plain-tag">--container-runtime=remote --runtime-request-timeout=15m --container-runtime-endpoint=unix:///var/run/crio/crio.sock</pre>
<p>重新加载配置，重启kubelet后，在K8S主节点上可以看到容器运行时变为cri-o：</p>
<pre class="crayon-plain-tag">NAME       AGE     VERSION   OS-IMAGE             KERNEL-VERSION           CONTAINER-RUNTIME
jade       6m18s   v1.12.1   Ubuntu 16.04.1 LTS   4.4.0-66-generic         cri-o://1.14.0-dev
# xenial-101 77d     v1.12.1   Ubuntu 16.04.3 LTS   4.13.0-41-generic        docker://1.13.1
# xenon      9d      v1.12.1   Ubuntu 16.04.4 LTS   4.15.0-34-generic        docker://18.6.1</pre>
<div class="blog_h3"><span class="graybg">拉取镜像</span></div>
<p>使用如下命令尝试拉取必备的镜像：</p>
<pre class="crayon-plain-tag">crictl pull docker.gmem.cc/k8s/kube-apiserver:v1.12.1          
crictl pull docker.gmem.cc/k8s/kube-controller-manager:v1.12.1 
crictl pull docker.gmem.cc/k8s/kube-scheduler:v1.12.1          
crictl pull docker.gmem.cc/k8s/kube-proxy:v1.12.1              
crictl pull docker.gmem.cc/k8s/pause:3.1                       
crictl pull docker.gmem.cc/k8s/etcd:3.2.24
crictl pull docker.gmem.cc/k8s/coredns:1.2.2
crictl pull k8s.gcr.io/pause:3.1
crictl pull docker.gmem.cc/calico/node:v3.2.3
crictl pull docker.gmem.cc/calico/cni:v3.2.3
crictl pull docker.gmem.cc/calico/kube-controllers:v3.2.3</pre>
<div class="blog_h3"><span class="graybg">运行Pod</span></div>
<p>kube-proxy等Daemonset管理的Pod会自动在所有节点上运行，你可以使用如下命令查看本机运行的Pod：</p>
<pre class="crayon-plain-tag"># 查看Pod
crictl pods
# POD ID              CREATED             STATE               NAME                             NAMESPACE           ATTEMPT
# 6914a958097c2       25 minutes ago      Ready               speaker-qkng4                    metallb-system      0

# 查看容器
crictl ps
# CONTAINER ID        IMAGE      CREATED             STATE               NAME</pre>
<p>如果默认运行时是runc，则此时看不到Kata容器：</p>
<pre class="crayon-plain-tag">kata-runtime list
# NONE</pre>
<p>我们可以创建一个标注为“不受信任”的Pod，这样CRI-O将会以Kata作为运行时来运行它：</p>
<pre class="crayon-plain-tag">kubectl label node jade k8s.gmem.cc/allow-untrusted-workload=true </pre><br />
<pre class="crayon-plain-tag">cat &lt;&lt;EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: ubuntu-untrusted
  annotations:
    io.kubernetes.cri-o.TrustedSandbox: "false"
spec:
  nodeSelector:
    k8s.gmem.cc/allow-untrusted-workload: "true"
  containers:
  - args:
    - -c
    - sleep 365d
    command:
    - /bin/sh
    image: docker.gmem.cc/ubuntu:16.04
    imagePullPolicy: Always
    name: ubuntu
EOF</pre>
<p>上述Pod运行起来后，你可以在节点上看到Kata容器、QEMU进程。 </p>
<div class="blog_h1"><span class="graybg">配置文件</span></div>
<div class="blog_h2"><span class="graybg">CRI-O配置</span></div>
<pre class="crayon-plain-tag">[crio]

# 注意，存储配置默认从 /etc/containers/storage.conf 读取
# 根目录，包括容器、镜像都存放在此
root = "/var/lib/containers/storage"
# 状态信息的根目录
runroot = "/var/run/containers/storage"
# 存储驱动
storage_driver = "overlay"

# 使用文件锁还是内存锁
file_locking = true

# 文件锁的位置
file_locking_path = "/run/crio.lock"


# kubelet/gRPC接口相关配置
[crio.api]

# 守护程序监听的AF_LOCAL接口
listen = "/var/run/crio/crio.sock"
# 流服务器监听的IP地址
stream_address = "127.0.0.1"
# 流服务器监听的端口
stream_port = "0"
# 流服务器是否启用TLS
stream_enable_tls = false
# 流服务器TLS证书配置
stream_tls_cert = ""
stream_tls_key = ""
stream_tls_ca = ""


# 使用哪些OCI运行时，如何管理这些OCI运行时
[crio.runtime]
# 管理网络命名空间的生命周期
manage_network_ns_lifecycle = true

# 为每个容器设置的默认ulimit，如果不指定从CRI-O守护进程继承
# 示例：nofile=1024:2048
default_ulimits = [
]

# 运行受信任工作负载的OCI运行时的路径，未来该属性将废弃
runtime = ""

# 默认运行时的名称，此名称定义在运行时映射配置项（Runtime mapping）中
default_runtime = "runc"

# 未来该属性将废弃，使用crio.runtime.runtimes代替此属性
# 此属性相当于在crio.runtime.runtimes中创建一个名为untrusted的运行时处理器
runtime_untrusted_workload = "/usr/bin/kata-runtime"

# 未来该属性将废弃 
# 工作负载的默认受信任级别，默认值trusted，可选untrusted。CRI-O根据信任级别选择适当的运行时
# kubelet可以将工作负载标记为不受信任或受信任
default_workload_trust = "trusted"

# 用于监控OCI运行时的公共二进制文件位置
conmon = "/usr/local/libexec/crio/conmon"

# 公共环境变量
conmon_env = [
        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
]

# 使用使用SELinux来隔离工作负载
selinux = false

# 默认的安全配置文件位置
seccomp_profile = "/etc/crio/seccomp.json"

# CRI-O默认的AppArmor配置的名称
apparmor_profile = "crio-default"

# 使用的CGroups管理机制
cgroup_manager = "cgroupfs"

# 容器默认特性列表
default_capabilities = [
        "CHOWN",
        "DAC_OVERRIDE",
        "FSETID",
        "FOWNER",
        "NET_RAW",
        "SETGID",
        "SETUID",
        "SETPCAP",
        "NET_BIND_SERVICE",
        "SYS_CHROOT",
        "KILL",
]

# 容器默认sysctls列表
default_sysctls = [
]

# 额外的设备列表
# 格式：device-on-host:device-on-container:permissions，例如/dev/sdc:/dev/xvdc:rwm
additional_devices = [
]

# OCI钩子目录，其中的钩子会自动执行
hooks_dir = [
]


# 每个容器默认挂载的文件
default_mounts_file = ""

# 容器中最大允许的线程数量
pids_limit = 1024

# 容器日志的最大尺寸
log_size_max = -1

# 容器退出文件存放位置
container_exits_dir = "/var/run/crio/exits"

# 容器Attach套接字的存放位置
container_attach_socket_dir = "/var/run/crio"

# 如果设置为true则所有容器运行在只读模式
read_only = false

# 日志输出级别
log_level = "info"

# 到宿主机的UID映射，格式containerUID:HostUID:Size，逗号分隔
uid_mappings = ""
# 到宿主机的UID映射，格式containerGID:HostGID:Size，逗号分隔
gid_mappings = ""

# 认为终止容器操作超时的最小时间
ctr_stop_timeout = 0

# 容器运行时映射表
[crio.runtime.runtimes.runc]
  runtime_path = "/usr/bin/runc"
[crio.runtime.runtimes.untrusted]
  runtime_path = "/usr/bin/kata-runtime"


# 管理OCI镜像的配置
# 默认从/etc/containers/registries.conf读取镜像仓库信息
[crio.image]
# 拉取镜像使用的默认传输协议
default_transport = "docker://"

# K8S pause镜像
pause_image = "k8s.gcr.io/pause:3.1"
pause_command = "/pause"

# 执行一个策略文件，此文件用于决定是否信任拉取的镜像
# 默认策略，例如/etc/containers/policy.json通常足够
signature_policy = ""

# 拉取非全限定名称的镜像时，从什么仓库拉取
registries = ['docker.io']


# CNI配置
[crio.network]
# CNI配置文件所在目录
network_dir = "/etc/cni/net.d/"
# CNI二进制文件所在目录
plugin_dir = "/opt/cni/bin/"</pre>
<div class="blog_h1"><span class="graybg">常用命令</span></div>
<div class="blog_h2"><span class="graybg">crictl</span></div>
<table class="full-width fixed-word-wrap" style="height: 56px;" width="1015">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">images</td>
<td>列出镜像</td>
</tr>
<tr>
<td class="blog_h3">pull</td>
<td>拉取镜像，默认配置的默认传输是docker://，因此可以直接使用docker pull语法拉取Docker镜像：<br />
<pre class="crayon-plain-tag">crictl pull --creds USERNAME[:PASSWORD] NAME[:TAG|@DIGEST]</pre>
</td>
</tr>
<tr>
<td class="blog_h3">inspecti</td>
<td>查看一个或多个镜像的状态：<br />
<pre class="crayon-plain-tag">crictl inspecti  docker.gmem.cc/ubuntu:16.04</pre>
</td>
</tr>
<tr>
<td class="blog_h3">imagefsinfo</td>
<td>查看镜像的文件系统信息</td>
</tr>
<tr>
<td class="blog_h3">stats</td>
<td>显示容器用量信息，包括CPU、内存、磁盘、INODES</td>
</tr>
<tr>
<td class="blog_h3">ps</td>
<td>列出容器</td>
</tr>
<tr>
<td class="blog_h3">info</td>
<td>显示容器运行时的信息：<br />
<pre class="crayon-plain-tag">crictl info 4ae949f913362 
{
  "status": {
    "conditions": [
      {
        "type": "RuntimeReady", # 运行时状态
      },
      {
        "type": "NetworkReady", # 网络状态
      }
    ]
  }
}</pre>
</td>
</tr>
<tr>
<td class="blog_h3">create</td>
<td>创建新容器 </td>
</tr>
<tr>
<td class="blog_h3">update</td>
<td>更新运行中的容器</td>
</tr>
<tr>
<td class="blog_h3">start</td>
<td>启动已经存在的容器</td>
</tr>
<tr>
<td class="blog_h3">stop</td>
<td>停止容器</td>
</tr>
<tr>
<td class="blog_h3">exec </td>
<td>在容器中执行命令：<pre class="crayon-plain-tag">crictl exec -it 4ae949f913362 sh</pre></td>
</tr>
<tr>
<td class="blog_h3">inspect</td>
<td>查看一个或多个容器的状态</td>
</tr>
<tr>
<td class="blog_h3">logs</td>
<td>查看容器日志</td>
</tr>
<tr>
<td class="blog_h3">pods</td>
<td>列出Pod</td>
</tr>
<tr>
<td class="blog_h3">runp</td>
<td>运行一个新的Pod：<pre class="crayon-plain-tag">crictl runp pod-config.yaml</pre></td>
</tr>
<tr>
<td class="blog_h3">stopp</td>
<td>停止一个Pod，注意必须传入Pod的ID而不是名称</td>
</tr>
<tr>
<td class="blog_h3">rmp</td>
<td>删除一个Pod，注意必须传入Pod的ID而不是名称</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/kata-containers-study-note">Kata Containers学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/kata-containers-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CoreOS知识集锦</title>
		<link>https://blog.gmem.cc/coreos-faq</link>
		<comments>https://blog.gmem.cc/coreos-faq#comments</comments>
		<pubDate>Wed, 12 Oct 2016 10:28:13 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Virtualization]]></category>
		<category><![CDATA[libvirt]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=13526</guid>
		<description><![CDATA[<p>简介 CoreOS是一个轻量级的Linux操作系统，CoreOS的自动化、安全性、可扩容性特征，让其非常适用于集群化的部署场景。 与其它的发行版不同，CoreOS没有包管理器，它倾向于在容器（例如Docker）中运行应用程序。CoreOS对流行的容器系统提供了开箱即用的支持。 CoreOS可以在云服务（例如EC2、GCE）、虚拟化平台（VMware、OpenStack、KVM）、裸金属值上运行。 Ignition 这是CoreOS提供的新的VM初始化机制，用于代替cloud-config。 Ignition能够执行磁盘分区、分区格式化、写入文件、配置用户、配置网络、创建RAID阵列等初始化操作。Ignition的运行时机非常的早，它在systemd启动之前、任何永久存储挂载之前即被调用。 在VM第一次（也仅仅是第一次）启动时，Ignition会从文件系统、URL、Hypervisor bridge读取JSON格式的配置文件，并将配置应用到VM。 运行CoreOS 即使是学习阶段，也最好创建3台CoreOS的集群，那样更容易认识CoreOS的特性。为了让CoreOS集群正常启动，你需要提供“点火配置”（ Ignition config ），或者通过user_data提供一个cloud-config。 libvirt/cloud-config 本节记录基于libvirt创建CoreOS客户机的详细过程。 获取镜像 我们可以下载CoreOS官网提供的qcow2格式的镜像，此镜像适用于QEMU： [crayon-69e59115efe1d488810635/] 下载完毕后，解压镜像到[crayon-69e59115efe21398332992-i/] 备用，默认扩展名是img，手工改成qcow2。 Config drive <a class="read-more" href="https://blog.gmem.cc/coreos-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/coreos-faq">CoreOS知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>CoreOS是一个轻量级的Linux操作系统，CoreOS的自动化、安全性、可扩容性特征，让其非常适用于集群化的部署场景。</p>
<p>与其它的发行版不同，CoreOS没有包管理器，它倾向于在容器（例如Docker）中运行应用程序。CoreOS对流行的容器系统提供了开箱即用的支持。</p>
<p>CoreOS可以在云服务（例如EC2、GCE）、虚拟化平台（VMware、OpenStack、KVM）、裸金属值上运行。</p>
<div class="blog_h1"><span class="graybg">Ignition</span></div>
<p>这是CoreOS提供的新的VM初始化机制，用于代替cloud-config。</p>
<p>Ignition能够执行磁盘分区、分区格式化、写入文件、配置用户、配置网络、创建RAID阵列等初始化操作。Ignition的运行时机非常的早，它在systemd启动之前、任何永久存储挂载之前即被调用。</p>
<p>在VM第一次（也仅仅是第一次）启动时，Ignition会从文件系统、URL、Hypervisor bridge读取JSON格式的配置文件，并将配置应用到VM。</p>
<div class="blog_h1"><span class="graybg">运行CoreOS</span></div>
<p>即使是学习阶段，也最好创建3台CoreOS的集群，那样更容易认识CoreOS的特性。为了让CoreOS集群正常启动，你需要提供<a href="https://coreos.com/blog/introducing-ignition.html">“点火配置”（ Ignition config ）</a>，或者通过user_data提供一个cloud-config。</p>
<div class="blog_h2"><span class="graybg">libvirt/cloud-config</span></div>
<p>本节记录基于libvirt创建CoreOS客户机的详细过程。</p>
<div class="blog_h3"><span class="graybg">获取镜像</span></div>
<p>我们可以下载CoreOS官网提供的qcow2格式的镜像，此镜像适用于QEMU：</p>
<pre class="crayon-plain-tag">wget https://stable.release.core-os.net/amd64-usr/current/coreos_production_qemu_image.img.bz2</pre>
<p>下载完毕后，解压镜像到<pre class="crayon-plain-tag">/home/alex/Vmware/KVM/coreos-20/vda.qcow2</pre> 备用，默认扩展名是img，手工改成qcow2。</p>
<div class="blog_h3"><span class="graybg">Config drive</span></div>
<p>为了配置CoreOS实例，我们需要在宿主机上创建一个目录，此目录中包含一些配置信息。此目录最终映射将为客户机上的一个文件系统。执行以下命令：</p>
<pre class="crayon-plain-tag">mkdir -p /home/alex/Vmware/KVM/coreos-20/cloud-config/openstack/latest/
touch /home/alex/Vmware/KVM/coreos-20/cloud-config/openstack/latest/user_data</pre>
<p>user_data是<a href="https://coreos.com/os/docs/latest/cloud-config.html">cloud config</a>格式的配置文件，它提供了客户机运行时所需要的定制化信息。我们至少需要在其中配置SSH Key以便（初次）登录到CoreOS中：</p>
<pre class="crayon-plain-tag">#cloud-config

ssh_authorized_keys:
 - ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBlsU4YrAi ... EIokU+jOd0MrsnOwQn9wJbov8Xhyw==</pre>
<div class="blog_h3"><span class="graybg">定义Domain</span></div>
<p>我们可以使用类似下面的命令来定义CoreOS的Domain：</p>
<pre class="crayon-plain-tag">virt-install --import --name coreos-20 --ram 1024 --vcpus 1
             --disk path=/home/alex/Vmware/KVM/coreos-20/vda.qcow2,format=qcow2,bus=virtio 
             --filesystem /home/alex/Vmware/KVM/coreos-20/cloud-config/,config-2,type=mount,mode=squash</pre>
<p>或者，在常规的Domain XML配置文件中，添加devices的子元素：</p>
<pre class="crayon-plain-tag">&lt;filesystem type='mount' accessmode='squash'&gt;
    &lt;source dir='/home/alex/Vmware/KVM/coreos-20/cloud-config/'/&gt;
    &lt;target dir='config-2'/&gt;
&lt;/filesystem&gt;</pre>
<div class="blog_h3"><span class="graybg">登录到CoreOS</span></div>
<p>默认情况下，CoreOS会尝试通过DHCP来获得自身的网络配置，我们可以在启动Domain后立即查看控制台，CoreOS的IP地址会打印在上面。执行下面的命令登录到CoreOS：</p>
<pre class="crayon-plain-tag">ssh -i path-to-key core@ip-of-coreos</pre>
<p>登录成功后，你可以为core用户设置密码，或者执行其它操作。 </p>
<div class="blog_h3"><span class="graybg">完整libvirt配置</span></div>
<pre class="crayon-plain-tag">&lt;domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'&gt;
    &lt;name&gt;coreos-20&lt;/name&gt;
    &lt;memory unit='MiB'&gt;1024&lt;/memory&gt;
    &lt;vcpu placement='static'&gt;1&lt;/vcpu&gt;
    &lt;os&gt;
        &lt;type arch='x86_64' machine='pc'&gt;hvm&lt;/type&gt;
    &lt;/os&gt;
    &lt;features&gt;
        &lt;acpi/&gt;
    &lt;/features&gt;
    &lt;cpu mode='custom' match='exact'&gt;
        &lt;model fallback='allow'&gt;SandyBridge&lt;/model&gt;
    &lt;/cpu&gt;
    &lt;clock offset='utc'/&gt;
    &lt;on_poweroff&gt;destroy&lt;/on_poweroff&gt;
    &lt;on_reboot&gt;restart&lt;/on_reboot&gt;
    &lt;on_crash&gt;destroy&lt;/on_crash&gt;
    &lt;devices&gt;
        &lt;emulator&gt;/usr/bin/qemu-system-x86_64&lt;/emulator&gt;
        &lt;disk type='volume' device='disk'&gt;
            &lt;driver name='qemu' type='qcow2'/&gt;
            &lt;source pool="default" volume="coreos-20.qcow2" /&gt;
            &lt;target dev='vda' bus='virtio'/&gt;
            &lt;boot order='1'/&gt;
        &lt;/disk&gt;
        &lt;filesystem type='mount' accessmode='squash'&gt;
            &lt;source dir='/home/alex/Vmware/libvirt/mount/coreos-base/cloud-config/'/&gt;
            &lt;target dir='config-2'/&gt;
        &lt;/filesystem&gt;
        &lt;controller type='usb' index='0'/&gt;
        &lt;controller type='pci' index='0' model='pci-root'/&gt;
        &lt;interface type='network'&gt;
            &lt;mac address='DE:AD:BE:EF:00:20'/&gt;
            &lt;source network='default'/&gt;
            &lt;model type='virtio'/&gt;
        &lt;/interface&gt;
        &lt;serial type='pty'&gt;
            &lt;target port='0'/&gt;
        &lt;/serial&gt;
        &lt;console type='pty'&gt;
            &lt;target type='serial' port='0'/&gt;
        &lt;/console&gt;
        &lt;input type='mouse' bus='ps2'/&gt;
        &lt;input type='keyboard' bus='ps2'/&gt;
        &lt;memballoon model='virtio'/&gt;
    &lt;/devices&gt;
&lt;/domain&gt;</pre>
<div class="blog_h2"><span class="graybg">libvirt/Ignition-config</span></div>
<p>当前推荐的配置CoreOS的方式是Ignition，但是目前libvirt没有对Ignition的直接支持，需要引入QEMU特有的配置片断。</p>
<p>Ignition配置为JSON格式，示例：</p>
<pre class="crayon-plain-tag">{
  "ignition": {
    "config": {},
    "timeouts": {},
    "version": "2.1.0"
  },
  "networkd": {},
  "passwd": {
    "users": [
      {
        "name": "core",
        "sshAuthorizedKeys": [
          "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBlsU4YrAif8Oh4Qdcq1SuF+CbPdr5T3DE3zzeYGG8nkcDMt/9dEjT8eHTMW+4BzCoIfYrIWIprJoykMnhZONBXnoXc/541tqU6MqF0ZRF0QlzSq6VLLLebG3zz+avdJSNLMAvolCLczP536EIokU+jOd0MrsnOwQn9wJbov8Xhyw=="
        ]
      }
    ]
  },
  "storage": {
    "files": [
      {
        "filesystem": "root",
        "group": {},
        "path": "/etc/hostname",
        "user": {},
        "contents": {
          "source": "data:,coreos-21",
          "verification": {}
        }
      }
    ]
  },
  "systemd": {}
}</pre>
<p>libvrit Domain配置示例：</p>
<pre class="crayon-plain-tag">&lt;domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'&gt;
    ...
    &lt;qemu:commandline&gt;
    	&lt;qemu:arg value="-fw_cfg"/&gt;
    	&lt;qemu:arg value="name=opt/com.coreos/config,file=/home/alex/Vmware/libvirt/mount/coreos-base/provision.ign"/&gt;
  &lt;/qemu:commandline&gt;
&lt;/domain&gt;</pre>
<p>注意：QEMU 2.0版本不支持 -fw_cfg，手工构建最新版本可以支持。 </p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/coreos-faq">CoreOS知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/coreos-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Docker学习笔记</title>
		<link>https://blog.gmem.cc/docker-study-note</link>
		<comments>https://blog.gmem.cc/docker-study-note#comments</comments>
		<pubDate>Tue, 20 Oct 2015 07:10:08 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Virtualization]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=13593</guid>
		<description><![CDATA[<p>基础知识 简介 Docker是一个容器化软件，所谓容器化即操作系统级别的虚拟化（Operating-system-level virtualization）。比起硬件虚拟化： 容器更加轻量，它不需要运行独立操作系统，因而减少了磁盘（操作系统文件占用GB+空间）、CPU（进程调度、硬件模拟等额外消耗）等基础资源的消耗，可扩容性更强 容器性能更高，虚拟硬件导致的低性能问题不复存在 容器启动非常迅速（小于1秒），普通VM启动时间可能需要1分钟 更加适合部署松耦合、分布式、弹性的微服务 容器化软件允许在同一个操作系统内核下存在多个相互隔离的用户空间实例，这些实例即被称为容器（Container）。从这些容器的所有者/用户的角度来看，它们就像是一个独立的服务器一样。除了隔离机制之外，容器化软件通常提供资源管理功能，限制一个容器的活动对其它容器的影响。 内核中用于支持容器化的特性： 名字空间机制，用于实现容器的隔离。名字空间包括： pid名字空间，不同空间中的PID可以重复 net名字空间，管理多个网络协议栈的实例 ipc名字空间，管理和访问IPC资源 mnt名字空间，管理文件系统的挂载点 控制组（Cgroups），用于控制容器的资源用量 UnionFS，联合文件系统 构建（Build）、分发（Ship）、运行（Run）是Docker提出的宣传口号，它的目标就是高效的完成这三件事，提高开发、测试、运维的效率： Docker将应用程序和它的运行环境（例如依赖、库）打包到一起，屏蔽不同运行环境的差异，实现可移植部署 Docker让应用程序在一个被隔离的容器中运行，多个应用程序可以依赖相互冲突的库却不相互干扰（尽管它们运行在单个内核中） 下图阐述了Docker和硬件虚拟化的区别： 使用流程 <a class="read-more" href="https://blog.gmem.cc/docker-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/docker-study-note">Docker学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">基础知识</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>Docker是一个容器化软件，所谓容器化即操作系统级别的虚拟化（Operating-system-level virtualization）。比起硬件虚拟化：</p>
<ol>
<li>容器更加轻量，它不需要运行独立操作系统，因而减少了磁盘（操作系统文件占用GB+空间）、CPU（进程调度、硬件模拟等额外消耗）等基础资源的消耗，可扩容性更强</li>
<li>容器性能更高，虚拟硬件导致的低性能问题不复存在</li>
<li>容器启动非常迅速（小于1秒），普通VM启动时间可能需要1分钟</li>
<li>更加适合部署松耦合、分布式、弹性的微服务</li>
</ol>
<p>容器化软件允许在<span style="background-color: #c0c0c0;">同一个操作系统内核下存在多个相互隔离的用户空间实例</span>，这些实例即被称为容器（Container）。从这些容器的所有者/用户的角度来看，它们就像是一个独立的服务器一样。除了隔离机制之外，容器化软件通常提供资源管理功能，限制一个容器的活动对其它容器的影响。</p>
<p>内核中用于支持容器化的特性：</p>
<ol>
<li>名字空间机制，用于实现容器的隔离。名字空间包括：
<ol>
<li>pid名字空间，不同空间中的PID可以重复</li>
<li>net名字空间，管理多个网络协议栈的实例</li>
<li>ipc名字空间，管理和访问IPC资源</li>
<li>mnt名字空间，管理文件系统的挂载点</li>
</ol>
</li>
<li>控制组（Cgroups），用于控制容器的资源用量</li>
<li>UnionFS，联合文件系统</li>
</ol>
<p><span style="background-color: #c0c0c0;">构建（Build）、分发（Ship）、运行（Run）</span>是Docker提出的宣传口号，它的目标就是高效的完成这三件事，提高开发、测试、运维的效率：</p>
<ol>
<li>Docker将应用程序和它的运行环境（例如依赖、库）打包到一起，屏蔽不同运行环境的差异，实现可移植部署</li>
<li>Docker让应用程序在一个被隔离的容器中运行，多个应用程序可以依赖相互冲突的库却不相互干扰（尽管它们运行在单个内核中）</li>
</ol>
<p>下图阐述了Docker和硬件虚拟化的区别：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2015/10/docker-vm-diff.png"><img class="aligncenter  wp-image-21067" src="https://blog.gmem.cc/wp-content/uploads/2015/10/docker-vm-diff.png" alt="docker-vm-diff" width="801" height="405" /></a></p>
<div class="blog_h2"><span class="graybg">使用流程</span></div>
<p>从用户的角度来看，使用Docker的典型工作流如下：</p>
<ol>
<li>将应用程序代码及其依赖纳入到Docker容器中
<ol>
<li>编写一个Dockerfile，描述执行环境，并拉取代码</li>
<li>如果应用程序依赖于外部应用（例如MySQL、Redis），你需要在某个仓库（例如Docker Hub）中找到它们。某些收费的外部应用可以在<a href="https://store.docker.com/">Docker Store</a>中找到</li>
<li>在Docker Compose file中引用应用程序，以及上面的那些外部应用，让他们能够同时运行</li>
<li>利用<a href="https://docs.docker.com/machine/overview/">Docker Machine</a>，在一个虚拟主机上构建、运行你的容器</li>
</ol>
</li>
<li>如果需要，为你的解决方案配置网络、存储</li>
<li>可选的，上传你的构建结果到仓库（私有、Docker官方），与团队成员协作</li>
<li>如果出现扩容（scale）需求，考虑使用<a href="https://docs.docker.com/engine/swarm/">Swarm</a>集群，通过<a href="https://docs.docker.com/ucp/overview/">Universal Control Plane</a>你可以方便的管理Swarm集群</li>
<li>最终，利用<a href="https://docs.docker.com/docker-cloud/overview/">Docker Cloud</a>将容器镜像部署到自有服务器或者云上</li>
</ol>
<div class="blog_h2"><span class="graybg">核心概念</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">概念</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Dockerfile</td>
<td>一段文本，Docker读取其中的指令以便自动化的构建Docker镜像。你可以在Dockerfile中声明任何命令</td>
</tr>
<tr>
<td>Docker Compose</td>
<td>一个工具，用来定义多容器（Multi-container）的Docker应用程序。你可以编写Composefile，来配置你的应用程序所依赖的服务，并通过单个命令来启动所有这些服务</td>
</tr>
<tr>
<td>Docker Engine</td>
<td>
<p>Docker的核心组件，它负责创建Docker镜像、运行Docker容器<br />从1.12版本开始，Docker引擎支持Swarm mode</p>
<p>包括三个组件：</p>
<ol>
<li>Docker守护程序，即dockerd</li>
<li>REST风格的接口，dockerd暴露的API</li>
<li>Docker命令行，通过REST API和dockerd通信，对其进行控制</li>
</ol>
</td>
</tr>
<tr>
<td>Docker Image</td>
<td>
<p>Docker镜像是一个文件系统 + 参数集，供Docker运行容器时使用。镜像本身不包含状态</p>
<p>任何人可以通过Docker镜像的方式来创建、分享软件</p>
<p>首次使用镜像时，会从仓库下载，之后，除非镜像的源码改变，不会再次下载</p>
</td>
</tr>
<tr>
<td>Layer</td>
<td>
<p>层，或者叫镜像层（Image layer），是指对<span style="background-color: #c0c0c0;">镜像的一个变更，或者指一个中间镜像</span>（intermediate image）。之所以叫层，和UFS（联合文件系统）有关，UFS允许：其包含的文件和目录可以分布在多个其它文件系统中，这些文件/目录（称为Branches）可以被叠加（overlay）形成单个新的文件系统</p>
<p>在Dockerfile中指定的指令，例如FROM/RUN/COPY，会导致先前的镜像发生变化，因而导致创建新的层</p>
<p>层有利于缩短构建的时间，Dockerfile发生变化后，变化之前的中间镜像不需要重新构建，可以作为缓存使用</p>
</td>
</tr>
<tr>
<td>Docker Container</td>
<td>
<p>Docker容器是Docker镜像的运行时实例</p>
<p>容器的行为取决于镜像如何被配置，可能是简单的执行一条命令，也可能是启动数据库这样的复杂服务</p>
</td>
</tr>
<tr>
<td>Docker Hub</td>
<td>
<p>一种服务，用于构建、管理镜像。其角色类似于Maven仓库或者PyPI</p>
<p>在Docker Hub上你可以很轻松下载到大量已经容器化的应用镜像，即拉即用。这些镜像中，有些是Docker官方维护的，更多的是众多开发者自发上传分享的</p>
<p>你可以将Github账号绑定到Docker Hub账号，并配置自动生成镜像的功能。这样，当Github中代码更新时，Docker镜像会自动更新</p>
</td>
</tr>
<tr>
<td>Docker Trusted Registry</td>
<td>DTR，企业级的Docker镜像存储方案，其角色类似于Docker Hub的私服</td>
</tr>
<tr>
<td>Docker Cloud</td>
<td>一种服务，能够构建、测试、部署镜像到你的主机上</td>
</tr>
<tr>
<td>Docker Universal Control Plane</td>
<td>UCP，管理Docker宿主机的集群，让它们整体上表现的像是单台机器</td>
</tr>
<tr>
<td>Docker Machine</td>
<td>一个工具，使用它你可以：
<ol>
<li>在Windows或者Mac平台上安装、运行Docker</li>
<li>基于命令docker-machine准备、管理多台Docker宿主机。管理行为包括：启动/停止/重启宿主机、升级Docker客户端和Daemon、配置Docker客户端，等等</li>
<li>准备Swarm集群</li>
</ol>
<p>当前，只有通过Docker Machine，你才能在Mac或者Windows上运行Docker。同时它也是管理<span style="background-color: #c0c0c0;">大量</span>基于各种Linux变体的宿主机的便捷方法</p>
</td>
</tr>
<tr>
<td>libcontainer</td>
<td>封装了名字空间、控制组、UnionFS的库，提供容器运行时基础功能</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">安装配置</span></div>
<p>本章主要介绍Ubuntu 14.04 LTS下安装、配置Docker的步骤。</p>
<div class="blog_h2"><span class="graybg">安装</span></div>
<p>你需要安装64bit的操作系统，内核的最低版本是3.10。为了支持aufs存储驱动，最好安装额外的内核包：</p>
<pre class="crayon-plain-tag">sudo apt-get update
sudo apt-get install linux-image-extra-$(uname -r) linux-image-extra-virtual</pre>
<p>添加Docker项目的APT源：</p>
<pre class="crayon-plain-tag">sudo echo  "deb https://apt.dockerproject.org/repo ubuntu-trusty main" &gt; /etc/apt/sources.list.d/docker.list
# 添加GPG Key
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
sudo apt-get update
# 可以查看当前系统支持的Docker引擎版本
apt-cache policy docker-engine</pre>
<p>安装Docker引擎并启动Docker守护程序：</p>
<pre class="crayon-plain-tag">sudo apt-get install docker-engine
sudo service docker start</pre>
<p>验证安装是否成功：</p>
<pre class="crayon-plain-tag"># 通知Docker引擎，将hello-world镜像载入到新建的容器中
docker run hello-world

# 执行下面的命令显示系统中容器的列表
docker ps -a
# 执行下面的命令显示系统中镜像的列表
docker images</pre>
<p>上述命令将下载一个测试目的的Docker镜像，并在一个容器中运行。该镜像会打印一条消息，然后退出。 </p>
<div class="blog_h2"><span class="graybg">升级与删除</span></div>
<p>执行下面的命令，来升级或者删除Docker：</p>
<pre class="crayon-plain-tag"># 升级
sudo apt-get upgrade docker-engine

# 删除
sudo apt-get purge docker-engine
sudo apt-get autoremove --purge docker-engine
# 上面的命令不会删除镜像、容器、卷或者用户创建的配置文件，你需要手工删除：
rm -rf /var/lib/docker</pre>
<div class="blog_h2"><span class="graybg">Windows和Mac</span></div>
<p>Docker最初是为Linux开发的，在Windows/Mac系统中，你可以借助Docker Machine来运行Docker。</p>
<p>从1.13开始，Windows和Mac具有基于原生虚拟化机制的Docker实现，不再依赖于Docker Machine。在Windows下，Docker基于Microsoft Hyper-V；在Mac下则基于HyperKit。</p>
<div class="blog_h3"><span class="graybg">Mac下命令自动完成</span></div>
<p>为Bash添加自动完成支持：</p>
<pre class="crayon-plain-tag">brew install bash-completion
brew tap homebrew/completions</pre>
<p>在.bash_profile中添加：</p>
<pre class="crayon-plain-tag">if [ -f $(brew --prefix)/etc/bash_completion ]; then  
    . $(brew --prefix)/etc/bash_completion
fi</pre>
<p>添加Docker的自动完成脚本：</p>
<pre class="crayon-plain-tag">pushd /usr/local/etc/bash_completion.d  
ln -s /Applications/Docker.app/Contents/Resources/etc/docker.bash-completion  
ln -s /Applications/Docker.app/Contents/Resources/etc/docker-machine.bash-completion  
ln -s /Applications/Docker.app/Contents/Resources/etc/docker-compose.bash-completion  </pre>
<div class="blog_h2"><span class="graybg">配置</span></div>
<p>执行一些必要的配置，可以让Ubuntu和Docker更好的一起工作。Docker配置文件默认为/etc/default/docker。</p>
<div class="blog_h3"><span class="graybg">创建Docker组</span></div>
<p>Docker的守护程序绑定到Unix套接字（而不是TCP/IP套接字）。默认的，该套接字的所有者是root，其它用户要访问它必须sudo。为此，Docker守护程序总是以root身份运行。</p>
<p>为了避免在调用<pre class="crayon-plain-tag">docker</pre> 命令时必须sudo，可以创建一个名为docker的UNIX组，并把你的用户添加到该组中。Docker守护程序启动时会为该组赋予读写权限</p>
<p>执行以下命令：</p>
<pre class="crayon-plain-tag">sudo groupadd docker
sudo usermod -aG docker $USER
# 用户$USER需要重新登录</pre>
<div class="blog_h3"><span class="graybg">调整内存和swap审计</span></div>
<p>当运行Docker时，你可能会看到类似下面的消息：</p>
<pre class="crayon-plain-tag">WARNING: Your kernel does not support cgroup swap limit.
WARNING: Yourkernel does not support swap limit capabilities. Limitation discarded.</pre>
<p>在主机上启用内存和swap审计，可以避免该消息：</p>
<ol>
<li>修改<pre class="crayon-plain-tag">/etc/default/grub</pre> ，设置内核选项：<br />
<pre class="crayon-plain-tag">GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"</pre>
</li>
<li>更新GRUB：<pre class="crayon-plain-tag">sudo update-grub</pre> </li>
<li>重新启动系统</li>
</ol>
<p>注意：启用后，即使不使用Docker，也会消耗额外1%左右的内存、降低10%左右的性能。</p>
<div class="blog_h3"><span class="graybg">启用UFW转发</span></div>
<p>如果在Docker的宿主机上使用<a href="/ubuntu-faq#ufw">UFW</a>，你需要额外的配置——由于UFW的默认行为是丢弃所有转发（路由）包，这会导致Docker无法正常工作。执行以下修改：</p>
<ol>
<li>修改配置文件/etc/default/ufw，设置<pre class="crayon-plain-tag">DEFAULT_FORWARD_POLICY="ACCEPT"</pre> </li>
<li>重新加载配置文件：<pre class="crayon-plain-tag">sudo ufw reload</pre> </li>
<li>允许针对Docker的入站连接：<pre class="crayon-plain-tag">sudo ufw allow 2375/tcp</pre> </li>
</ol>
<div class="blog_h3"><span class="graybg">配置DNS</span></div>
<p>Ubuntu及其衍生的桌面版Linux，通常会自动设置/etc/resolv.conf，将127.0.0.1作为默认DNS，同时网络管理器启用dnsmasq，将DNS请求代理给真实的DNS服务器</p>
<p>在这样的配置下，启动Docker容器会导致如下警告：</p>
<pre class="crayon-plain-tag">WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers
can't use it. Using default external servers : [8.8.8.8 8.8.4.4]</pre>
<p>出现此错误是因为Docker容器不能使用本地DNS服务器127.0.0.1，因此它默认使用了谷歌的DNS服务器</p>
<p>要避免此警告，依次执行：</p>
<ol>
<li>修改<pre class="crayon-plain-tag">/etc/default/docker</pre> 设置<pre class="crayon-plain-tag">DOCKER_OPTS="--dns 10.0.0.1"</pre> ，其中10.0.0.1替换为你的内网DNS服务器。注意<pre class="crayon-plain-tag">--dns</pre> 选项可以指定多次，对应多个备选DNS服务器</li>
<li>重新启动Docker守护程序：<pre class="crayon-plain-tag">sudo service docker restart</pre> </li>
</ol>
<p>你也可以修改网络管理器配置，/etc/NetworkManager/NetworkManager.conf，禁用dnsmasq：</p>
<pre class="crayon-plain-tag"># 注释掉：
dns=dnsmasq</pre>
<div class="blog_h3"><span class="graybg">设置自启动</span></div>
<p>14.10以下的版本，已经自动通过upstart配置Docker为自启动。15.04版本开始，Ubuntu使用systemd作为启动/服务管理器，你需要执行：</p>
<pre class="crayon-plain-tag">sudo systemctl enable docker </pre>
<div class="blog_h3"><span class="graybg">改变存储目录</span></div>
<p>默认情况下，Docker将/var/lib/docker作为基目录，并在其子目录下存储下载的镜像，以及运行过的容器，可以为Docker序指定选项-g以改变基目录：</p>
<pre class="crayon-plain-tag">DOCKER_OPTS=" -g /home/alex/Vmware/docker" </pre>
<p>你也可以使用符号链接来改变镜像的位置。</p>
<div class="blog_h3"><span class="graybg">为Docker Hub指定Mirror</span></div>
<p>国内访问Docker Hub比较缓慢，可以使用<a href="https://dashboard.daocloud.io/">DaoCloud</a>提供的Mirror（需要1.3.2+）。注册DaoCloud的账号后，进入仪表盘页面，点击“加速器”链接，在新的页面上会显示类似下面的命令：</p>
<pre class="crayon-plain-tag">curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://0aa2e1e9.m.daocloud.io</pre>
<p>以root身份执行上面的命令后，文件/etc/docker/daemon.json被修改，添加以下内容：</p>
<pre class="crayon-plain-tag">{"registry-mirrors": ["http://0aa2e1e9.m.daocloud.io"]}</pre>
<p>你也可以手工修改/etc/default/docker文件，设置DOCKER_OPTS，追加：</p>
<pre class="crayon-plain-tag">DOCKER_OPTS= "  --registry-mirror http://0aa2e1e9.m.daocloud.io"</pre>
<p>此外，也可以<a href="https://link.jianshu.com/?t=https%3A%2F%2Fcr.console.aliyun.com%2F%23%2Faccelerator">注册阿里云开发者账号</a>，获得自己的Mirror URL。</p>
<div class="blog_h3"><span class="graybg">独立于守护进程运行</span></div>
<p>默认的，当Docker守护程序退出时，所有正在运行的容器被自动关闭。自1.12开始，你可以配置守护程序，让容器在守护程序退出后保持运行状态。</p>
<p>要启用该特性，可以使用选项<pre class="crayon-plain-tag">sudo dockerd --live-restore</pre> ，或者在/etc/docker/daemon.json中配置：</p>
<pre class="crayon-plain-tag">{
    "live-restore": true
}</pre>
<p>该特性于Swarm模式不兼容。 </p>
<div class="blog_h1"><span class="graybg">Docker入门</span></div>
<div class="blog_h2"><span class="graybg">构建镜像</span></div>
<p>whalesay是Docker官方提供的一个学习用镜像，运行此镜像，可以在屏幕上显示一头鲸和一句话：</p>
<pre class="crayon-plain-tag"># docker/whalesay为镜像名
# cowsay为执行的命令，boo-boo为命令行参数，即鲸说的那句话
docker run docker/whalesay cowsay boo-boo</pre>
<p>本节我们以whalesay镜像为基础，学习创建自己的Docker镜像</p>
<div class="blog_h3"><span class="graybg">编写Dockerfile</span></div>
<p>在前面的章节我们提到过，Dockerfile用于描述镜像如何被构建。创建镜像的第一步就是编写Dockerfile。</p>
<p>新建一个目录作为构建镜像的上下文目录，所谓上下文目录，意味着构建过程所需的全部文件都位于其中：</p>
<pre class="crayon-plain-tag">mkdir mywhalesay
cd mywhalesay

touch Dockerfile</pre>
<p>编辑Dockerfile文件，添加以下内容： </p>
<pre class="crayon-plain-tag"># FROM关键字说明当前镜像基于哪个镜像来构建
FROM docker/whalesay:latest

# RUN关键字用于在构建时执行任意命令，这里安装fortunes，能够随机的输出名人名言
# 能够正常使用apt-get是因为docker/whalesay FROM ubuntu:14.04
RUN apt-get -y update &amp;&amp; apt-get install -y fortunes

# CMD关键字用来指定镜像被加载后执行的命令，现在这头鲸会自顾自的引用名言了
CMD /usr/games/fortune -a | cowsay</pre>
<div class="blog_h3"><span class="graybg">执行构建</span></div>
<p>在上下文执行下面的命令以构建镜像：</p>
<pre class="crayon-plain-tag">docker build -t mywhalesay .</pre>
<p> 构建过程中控制台会打印详细过程，说明如下：</p>
<pre class="crayon-plain-tag"># 发送构建上下文给守护程序检查，确保构建需要的文件完备
# Sending build context to Docker daemon 2.048 kB
# 第一步，加载镜像docker/whalesay到临时容器，后续RUN关键字就是在修改此容器
Step 1 : FROM docker/whalesay:latest
 ---&gt; 6b362a9f73eb   # 这里显示的是镜像ID
# 第二步，在临时容器中运行命令，安装新软件，这导致容器的文件系统发生改变，与其镜像变得不同
Step 2 : RUN apt-get -y update &amp;&amp; apt-get install -y fortunes
 ---&gt; Running in f8c9dec97efc  # 这里显示的是容器ID
Processing triggers for libc-bin (2.19-0ubuntu6.6) ...
 ---&gt; 17c653933644
# 移除前面的临时容器
Removing intermediate container f8c9dec97efc
# 第三步，固化前面临时容器对docker/whalesay的改变，作为新的镜像fd35a325caf4
# 创建新的临时容器，尝试运行CMD关键字指定的命令
Step 3 : CMD /usr/games/fortune -a | cowsay
 ---&gt; Running in 8ae48258f958  
 ---&gt; fd35a325caf4  # 新的镜像
Removing intermediate container 8ae48258f958
# 构建完毕
Successfully built fd35a325caf4</pre>
<div class="blog_h3"><span class="graybg">运行新镜像</span></div>
<p>构建过程不会对上下文目录进行修改，构建好的镜像自动存放到/var/lib/docker目录下。可以运行命令查看：</p>
<pre class="crayon-plain-tag">docker images
# REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
# mywhalesay          latest              c779eaff986e        2 minutes ago       275 MB
# docker/whalesay     latest              6b362a9f73eb        17 months ago       247 MB</pre>
<p> 好了，看看这头智慧鲸都说了些什么吧：</p>
<pre class="crayon-plain-tag">docker run mywhalesay</pre>
<div class="blog_h2"><span class="graybg">使用Docker Hub</span></div>
<p>首先你需要<a href="https://hub.docker.com">注册</a>一个账号， 然后创建一个仓库（Repository），这里的仓库类似于Git仓库，它以你的Docker Hub用户名作为默认的名字空间，例如gmemcc/mywhalesay。</p>
<div class="blog_h3"><span class="graybg">打标签</span></div>
<p>标签（tag）是用于<span style="background-color: #c0c0c0;">区分镜像变体</span>的一种方法。</p>
<p>利用tag命令，你可以把一个本地镜像关联到Docker Hub仓库。首先，查询镜像的ID：</p>
<pre class="crayon-plain-tag">docker images 
# REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
# mywhalesay          latest              baa64efc0f1d        6 minutes ago       275 MB</pre>
<p>为之前构建的本地镜像mywhalesay打标签：</p>
<pre class="crayon-plain-tag">docker tag baa64efc0f1d gmemcc/mywhalesay:latest
# 可以删除本地镜像
docker rmi -f baa64efc0f1d</pre>
<p>输入Docker Hub账号密码以登录：</p>
<pre class="crayon-plain-tag">docker login</pre>
<div class="blog_h3"><span class="graybg">PUSH操作</span></div>
<p>使用push子命令，可以把本地镜像推送到Docker Hub上的仓库中： </p>
<pre class="crayon-plain-tag">docker push gmemcc/mywhalesay</pre>
<p>推送完毕后，可以到仓库主页去看看Tags选项卡，会出现一个lastest标签。</p>
<div class="blog_h3"><span class="graybg">PULL操作</span></div>
<p>在运行容器、构建镜像时，只要所需镜像在本地不存在，都会执行pull子命令拉取镜像。你也可以手工的拉取镜像：</p>
<pre class="crayon-plain-tag">docker pull gmemcc/mywhalesay </pre>
<div class="blog_h2"><span class="graybg">搭建Docker私服</span></div>
<p>为了方便企业内部Docker镜像的管理，你可以搭建Docker私服（registry server）。最简单的私服例子如下：</p>
<pre class="crayon-plain-tag">docker run -d -p 5000:5000 --restart=always -h registry --network local --dns 172.21.0.1  --ip 172.21.0.4 --name registry registry:2
# 如果宿主机是ARM平台，考虑使用镜像 budry/registry-arm
# 否则你会收到错误 docker: no supported platform found in manifest list.</pre>
<p>现在，你可以从Docker Hub上拉取镜像，并tag到私服中：</p>
<pre class="crayon-plain-tag">docker pull ubuntu
docker tag ubuntu localhost:5000/ubuntu</pre>
<p>然后，推送镜像到私服：</p>
<pre class="crayon-plain-tag">docker push localhost:5000/ubuntu</pre>
<p>之后，你可以再把镜像拉取下来：</p>
<pre class="crayon-plain-tag">docker pull localhost:5000/ubuntu</pre>
<div class="blog_h3"><span class="graybg">存储</span></div>
<p>默认情况下，私服中的数据存放在Docker卷中，此卷位于宿主机文件系统中。要改变存放位置，可以指定选项：</p>
<pre class="crayon-plain-tag">--volume /path/on/host:/var/lib/registry</pre>
<div class="blog_h3"><span class="graybg">外部访问</span></div>
<p>要想其它宿主机能访问私服，最好启用TLS，其配置类似于Web服务器的SSL配置：</p>
<pre class="crayon-plain-tag"># 创建密钥对
openssl genrsa -out zircon.local.key 4096
openssl req -new -x509 -days 3650 -text -key zircon.local.key -out zircon.local.crt

# 把证书复制到特定目录
mkdir -p /etc/docker/certs.d/docker.gmem.cc
sudo cp zircon.local.crt /etc/docker/certs.d/zircon.local\:5000/domain.crt
# 非本机：
scp alex@zircon.local:~/Vmware/docker/registry/certs/zircon.local.crt /etc/docker/certs.d/zircon.local\:5000/domain.crt

# 可选：全局启用证书
cp zircon.local.crt /usr/share/ca-certificates/
# 启用zircon.local的证书
sudo dpkg-reconfigure ca-certificates
# 需要重启服务
sudo service docker restart

# 重新运行私服
docker run -d -p 5000:5000 --restart=always --name registry \
 -v /home/alex/Vmware/docker/registry/certs:/certs \
 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/zircon.local.crt \
 -e REGISTRY_HTTP_TLS_KEY=/certs/zircon.local.key \
 registry:2</pre>
<p>这样，在网络中的机器都可以使用域名zircon.local代替localhost来访问私服了。</p>
<p>Mac版本的Docker目前无法访问自签名的私服，需要配置：</p>
<pre class="crayon-plain-tag">"insecure-registries":["zircon.local"]</pre>
<div class="blog_h3"><span class="graybg">使用letsencrypt证书</span></div>
<p>只需要注意把cert.pem、chain.pem合并到一起，作为证书即可：</p>
<pre class="crayon-plain-tag">cd /etc/letsencrypt/live/docker.gmem.cc/
cp privkey.pem domain.key
cat cert.pem chain.pem &gt; domain.crt
chmod 777 domain.crt
chmod 777 domain.key </pre>
<div class="blog_h3"><span class="graybg">访问控制</span></div>
<p>除了启用TLS，你可能还需要进行用户身份验证。首先，在宿主机上建立一个目录，并生成密码文件：</p>
<pre class="crayon-plain-tag">cd /home/alex/Vmware/docker/registry/
mkdir auth
# 生成密码，输出到宿主机文件
docker run --entrypoint htpasswd registry:2 -Bbn user passwd &gt; auth/htpasswd</pre>
<p>然后，运行私服： </p>
<pre class="crayon-plain-tag">docker run -d -p 5000:5000 --restart=always --name registry \
  -v /home/alex/Vmware/docker/registry/auth:/auth \
  -e "REGISTRY_AUTH=htpasswd" \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  -v /home/alex/Vmware/docker/registry/certs:/certs \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/zircon.local.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/zircon.local.key \
  registry:2</pre>
<p>现在你可以登录到私服了： </p>
<pre class="crayon-plain-tag">docker login docker.gmem.cc </pre>
<div class="blog_h2"><span class="graybg">管理容器</span></div>
<p>要运行容器，首先要获得相应的镜像。运行容器时，如果镜像在本地不存在，Docker会自动到仓库下载。默认的仓库是Docker Hub。执行docker run命令可以启动一个容器：</p>
<pre class="crayon-plain-tag"># 不指定tag，默认使用lastest
docker run ubuntu
# 指定tag
docker run ubuntu:lastest
docker run ubuntu:14.04 </pre>
<div class="blog_h3"><span class="graybg">基本用法</span></div>
<p>将镜像加载到容器中后，相当于得到一个预装了某些软件的操作系统。例如对于ubuntu镜像，你可以： </p>
<pre class="crayon-plain-tag"># 像使用普通虚拟机一样使用容器：

# 调用echo命令
docker run ubuntu /bin/echo 'Hello world'

# 交互式的命令行
# -t 在容器内分配Terminal或者伪TTY
# -i 启动一个交互式的连接
docker run -t -i ubuntu /bin/bash

# -d 在后台运行容器（daemonize）。启动后，Docker会输出当前容器的ID
docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
# 216dd35d275a6c6e1e548232bdd9db7cab8a6b2f9078367a3c842762dc458c1a

# 查看正在运行的容器，由于容器ID太长，难以记忆，因此Docker会为每一个容器生成一个名字，例如gloomy_bohr。这个名字可以修改
docker ps
# CONTAINER ID IMAGE     COMMAND                  CREATED          STATUS         PORTS    NAMES
# 216dd35d275a  ubuntu   "/bin/sh -c 'while tr"   9 seconds ago    Up 8 seconds            gloomy_bohr

# 查看所有已创建的容器，不管容器是否正在运行
docker ps --all

# 指定容器名称，容器名称必须具有唯一性
docker run --name webserver training/webapp python app.py 

# 查看容器运行日志，打印标准输出
docker logs gloomy_bohr

# 在任何时候，你可以附到一个运行中的容器，grab其标准输入/输出
docker attach gloomy_bohr

# 停止容器。超时容器未停止，则发送SIGKILL信号
docker stop gloomy_bohr

# 启动容器
docker start gloomy_bohr

# 在一个正在运行的容器中执行命令
docker exec gloomy_bohr ping 8.8.8.8
# 连接到容器，交互式的执行命令
docker exec -it ubuntu-16.04

# 查看容器内运行的进程
docker top gloomy_bohr
# 以JSON格式打印容器的详细配置信息
docker inspect gloomy_bohr

# 删除容器
# -f 即使正在运行，也将容器删除
docker rm -f gloomy_bohr</pre>
<p>上面的每个docker命令调用，实际上都在使用<span style="background-color: #c0c0c0;">Docker客户端</span>。Docker是基于Go语言开发的，执行<pre class="crayon-plain-tag">version</pre> 子命令可以查看客户端、服务器的版本。</p>
<div class="blog_h3"><span class="graybg">运行网络应用</span></div>
<p>Docker的主要应用场景在服务器端，而后者运行的程序大多是网络应用。这里我们使用training/webapp这个镜像来说明如何在容器中运行Web服务器：</p>
<pre class="crayon-plain-tag"># -P 自动将容器中的监听端口映射到宿主机的某个端口
docker run -d -P --name web training/webapp python app.py
# 执行下面的命令查看端口如何映射
docker ps -l
# PORTS  端口映射情况位于该列
# 0.0.0.0:32769-&gt;5000/tcp   宿主机的32769端口映射到容器的5000端口

# -p 手工指定端口映射，宿主机的80端口映射的哦容器的5000端口
docker run -d -p 80:5000 training/webapp python app.py

# 查询端口映射
docker port hungry_payne 5000

# 查看Web应用日志
# -f的效果类似于tail -f
docker logs -f hungry_payne</pre>
<div class="blog_h2"><span class="graybg">管理网络</span></div>
<p>利用网络驱动（network drivers） ，Docker为容器提供了网络支持。网络驱动主要有两类：bridge、overlay。默认随着Docker引擎一起安装了三个网络：</p>
<pre class="crayon-plain-tag">docker network ls
# NETWORK ID          NAME                DRIVER              SCOPE
# f160b7cc8b94        bridge              bridge              local               
# fc47bb582730        host                host                local               
# 5ac20f3697f3        none                null                local</pre>
<p>除非明确指定，新启动的容器总是使用<span style="background-color: #c0c0c0;">bridge</span>这个网络。 </p>
<p>执行network inspect子命令可以查看一个网络的详细信息，包括它为各连接到的容器分配的IP地址：</p>
<p>执行：<pre class="crayon-plain-tag">docker network inspect bridge</pre> ，结果如下：</p>
<pre class="crayon-plain-tag">[
    {
        "Name": "bridge",
        "Id": "f160b7cc8b94b782e5fed8b2e50e72f14ad205917540b1dc464f509f7eb11dec",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        /* 连接到此网络的容器的集合 */
        "Containers": {
            "87afe74d233dcd2e6caf2d569c785b0457123d4ad25f1c1bd9850576ba3afd1c": {
                "Name": "web",
                "EndpointID": "e8a0db5f066fd13bc2cb432f375582cb92e445a982a0ed667d26d92adb19550f",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]</pre>
<p>要把一个容器从网络中移除，可以执行：</p>
<pre class="crayon-plain-tag"># 把web从网络bridge中移除
docker network disconnect bridge web</pre>
<div class="blog_h3"><span class="graybg">创建网络</span></div>
<p>要创建一个简单的网络，可以执行：</p>
<pre class="crayon-plain-tag">docker network create -d bridge newbridge</pre>
<p>类似的，再不需要时，可以删除自定义网络：</p>
<pre class="crayon-plain-tag">docker network rm newbridge</pre>
<p>注意：名为bridge的默认网络不能被删除。</p>
<div class="blog_h3"><span class="graybg">添加容器到网络</span></div>
<p>如果不希望容器使用默认网络，可以在运行容器的时候指定--network参数：</p>
<pre class="crayon-plain-tag">docker run -d --network=newbridge --name db training/postgres
# 获得容器的IP地址
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' db</pre>
<p>Docker允许在运行时将容器添加到任意多个网络：</p>
<pre class="crayon-plain-tag">docker network connect bridge db</pre>
<div class="blog_h2"><span class="graybg">管理数据卷</span></div>
<p>所谓数据卷（Data volumes），是一个或者多个容器中<span style="background-color: #c0c0c0;">特定的目录</span>，这些目录<span style="background-color: #c0c0c0;">绕过容器的联合文件系统</span>（UFS，可以将不同物理位置合并mount到Linux目录树的同一位置） ，数据卷的一系列特性有利于数据的持久化、共享：</p>
<ol>
<li>数据卷在容器被创建时初始化，如果容器使用的基础镜像在数据卷的挂载点上包含数据，则这些数据被拷贝到新初始化的数据卷中。注意：当挂载宿主机目录作为数据卷时，拷贝行为不发生</li>
<li>数据卷可以被多个容器共享，或者被重用</li>
<li>对数据卷的更改是直接进行的</li>
<li>当你升级基础镜像时，对数据卷的修改不被包含其中</li>
<li>即使容器本身被删除，数据卷依然存在，数据卷独立于容器的生命周期</li>
</ol>
<p>注意：<span style="background-color: #c0c0c0;">作为挂载点的容器目录，其原有的文件全部不可见</span>，不会进行Overlay。</p>
<div class="blog_h3"><span class="graybg">添加数据卷</span></div>
<p>要添加匿名数据卷，可以在启动容器时指定-v参数：</p>
<pre class="crayon-plain-tag"># 在容器的/webapp位置创建一个匿名数据卷
docker run -d -P --name web -v /webapp training/webapp python app.py</pre>
<p>在制作镜像时，你可以用VOLUME指令声明一个或者多个数据卷，任何基于此镜像的容器，自动添加这些数据卷。</p>
<div class="blog_h3"><span class="graybg">定位数据卷</span></div>
<p>匿名数据卷在宿主机上以目录的形式存在，要定位此目录，可以执行inspect子命令：</p>
<pre class="crayon-plain-tag">/* docker inspect web的部分输出 */
"Mounts": [
    {
        "Name": "356707e2ddc02715db78b958b623707c2475f66258c14b68de48f0e29188bc0f",
        /* 在宿主机上的存放位置 */
        "Source": "/mnt/c3d88ac1-b4d5-4cdd-86b4-4255aba9ddb1/docker/volumes/35670b68de.../_data",
        "Destination": "/webapp",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
]</pre>
<div class="blog_h3"><span class="graybg">挂载宿主机目录</span></div>
<p>你也可以把任意宿主机目录挂载为数据卷，映射到容器的目录树中，仍然使用-v参数： </p>
<pre class="crayon-plain-tag"># 宿主机目录/src/webapp被挂载到容器的/webapp
# 容器中的挂载点必须总是指定绝对路径
# 宿主机目录可以指定绝对路径，也可以指定一个名称，Docker引擎依据此名称创建/引用命名卷
docker run -d -P --name web -v /src/webapp:/webapp training/webapp python app.py</pre>
<p>上面的例子中，如果容器的基础镜像已经包含了/webapp目录，则它会被宿主机的/src/webapp覆盖，但是容器从基础镜像得到的/webapp中的内容不会被删除，一旦数据卷被卸载，则/webapp中的内容恢复原样。</p>
<div class="blog_h3"><span class="graybg">挂载宿主机文件</span></div>
<p>挂载单个宿主机文件也被支持：<pre class="crayon-plain-tag">-v ~/.bash_history:/root/.bash_history</pre> </p>
<div class="blog_h3"><span class="graybg">挂载共享存储</span></div>
<p>除了挂载宿主机的本地目录，你也可以挂载共享存储为数据卷，Docker通过Volume plugins来支持iSCSI、NFS、FC等共享存储。使用共享存储的好处是它们是不依赖于主机的。</p>
<p>你可以在启动容器时，即时的在共享存储上创建<span style="background-color: #c0c0c0;">命名卷（named volume）</span>：</p>
<pre class="crayon-plain-tag"># --volume-driver 指定卷驱动，可选
# my-named-volume为新创建的卷的名字
docker run -d -P  --volume-driver=flocker   -v my-named-volume:/webapp  
    --name web training/webapp python app.py</pre>
<p>或者，先手工创建命名卷，然后引用命名卷：</p>
<pre class="crayon-plain-tag">docker volume create -d flocker -o size=20GB my-named-volume
docker run -d -P -v my-named-volume:/webapp</pre>
<p>例子中的flocker是一个专为Docker设计的数据卷管理工具。flocker管理的数据卷可以位于集群中的任何主机上，而不是绑定到某台宿主机。</p>
<p>注意：</p>
<ol>
<li>命名卷对应/var/lib/docker/volumes/my-named-volume/_data目录。该目录会自动创建</li>
<li>容器A、B共享一个命名卷，则A的写入B可以看到</li>
<li>A使用 -v my-named-volume:/data，则data中原有（镜像自带）的文件可以在my-named-volume中看到</li>
</ol>
<div class="blog_h3"><span class="graybg">挂载为只读</span></div>
<p>数据卷可以挂载为只读：<pre class="crayon-plain-tag">-v /src/webapp:/webapp:ro #添加:ro后缀</pre> </p>
<div class="blog_h3"><span class="graybg">备份/恢复</span></div>
<p>可以参考下面的命令，来备份一个数据卷：</p>
<pre class="crayon-plain-tag"># --volumes-from  引用来自dbstore的数据卷/dbdata
# -v 挂载宿主机当前目录到/backup
# 容器启动后，使用tar命令，将/dbdata的内容压缩，存放到/backup，亦即宿主机的当前目录
# tar完成后，容器停止，备份完毕
docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata</pre>
<p>使用类似的方法，可以首先数据卷的恢复。 跨宿主机联用备份/恢复，可以实现数据卷迁移。</p>
<div class="blog_h3"><span class="graybg">删除数据卷</span></div>
<p>默认情况下，容器删除后，其挂载的数据卷会被保留。一般的，你可以指定：当容器被删除后，自动清理匿名卷：</p>
<pre class="crayon-plain-tag"># --rm示意自动清理匿名卷/foo
# 挂载到/bar的命名卷awesone不会被清理
docker run --rm -v /foo -v awesome:/bar busybox top</pre>
<p>数据卷可以通过--volumes-from选项被其它容器引用，这种情况下，创建/引用数据卷的全部容器被删除之前，数据卷是无法删除的。要在删除容器时，同时删除数据卷，可以指定-v选项，例如</p>
<pre class="crayon-plain-tag"># 删除db3的同时，删除其使用的数据卷。注意，db3必须是最后一个使用它引用的所有数据卷的容器
docker rm -v db3</pre>
<div class="blog_h3"><span class="graybg">数据卷容器</span></div>
<p>如果需要在多个容器之间共享持久化数据，或者期望在非持久化容器中使用持久化数据，最好的方法是创建命名数据卷容器——仅仅为了提供共享数据卷的容器，并在其中挂载数据卷供其它容器引用。</p>
<p>首先创建一个命名的容器，但是不需要它执行任何命令：</p>
<pre class="crayon-plain-tag"># 创建一个名为dbstore的容器，其包含一个数据卷/dbdata
docker create -v /dbdata --name dbstore training/postgres /bin/true
# 注意 -v 非常重要，如果不声明-v，则此容器的目录不会暴露出去
docker run -v /jdk --name jdk docker.gmem.cc/jdk:7u80</pre>
<p>然后，你可以利用选项--volumes-from，在其它容器中挂载dbstore的/dbdata目录 ：</p>
<pre class="crayon-plain-tag">docker run -d --volumes-from dbstore --name db1 training/postgres
# 再创建一个容器，与db1共享来dbstore的/dbdata。两个容器的共享卷的挂载路径一致
docker run -d --volumes-from dbstore --name db2 training/postgres</pre>
<p>这样启动db1、db2后，如果postgres镜像包含/dbdata目录，它将被来自dbstore的数据卷mask掉，仅dbstore的/dbdata对于db1、db2可见。</p>
<p>选项--volumes-from 可以指定多次，这样你可以联合使用来自多个容器的数据卷。</p>
<p>选项--volumes-from可以通过链式的结构扩展——链条上的后面的容器可以使用前面任意容器声明的数据卷：</p>
<pre class="crayon-plain-tag"># 由于db1使用了dbstore的数据卷/dbdata
# 因此db3也可以使用
docker run -d --name db3 --volumes-from db1 training/postgres</pre>
<p>注意，<span style="background-color: #c0c0c0;">提供数据卷的容器不需要处于运行状态</span>。</p>
<div class="blog_h3"><span class="graybg">共享存储陷阱</span></div>
<p>多个容器共享一个数据卷时，可能导致数据破坏，这是由并发的写操作导致的。并发写操作可能来自于容器或者宿主机。 </p>
<div class="blog_h1"><span class="graybg">镜像</span></div>
<div class="blog_h2"><span class="graybg">基本镜像</span></div>
<p>所谓基本镜像（Base image），一般是指没有父镜像的镜像。此类镜像打包一个空白的操作系统。制作基本镜像的步骤依赖于你想打包的Linux发行版。</p>
<div class="blog_h3"><span class="graybg">使用Tar创建完整镜像</span></div>
<p>一般情况下，你可以在这样的Linux操作系统下完成基本镜像的创建——该操作系统就是你希望打包的Linux发行版。</p>
<p>某些工具可以简化镜像的创建，例如<a href="https://wiki.debian.org/Debootstrap">Debootstrap</a>可以安装一个Debian/Ubuntu的基本操作系统到一个目录中。使用该工具的示例：</p>
<pre class="crayon-plain-tag"># 下载Ubuntu 16.04（xenial）基本操作系统到xenial目录
sudo debootstrap xenial xenial

# tar -C切换工作目录 -c 创建压缩文件
# 压缩结果通过管道传递给docker import
# import子命令创建一个空白文件系统镜像，然后把Tar的内容导入进去
# ubuntu:16.04 创建一个名为ubuntu的仓库，Tag为16.04
sudo tar -C xenial -c . | docker import - ubuntu:16.04</pre>
<p>其它创建基本镜像的脚本，可以参考<a href="https://docs.docker.com/engine/userguide/eng-image/baseimages/">Create a base image </a></p>
<div class="blog_h3"><span class="graybg">扩展scratch镜像</span></div>
<p>scratch是Docker保留的名称，它不是一个镜像，你也不能把任何仓库命名为scratch。scratch可以作为构建基本镜像的起点：</p>
<pre class="crayon-plain-tag">FROM scratch
ADD hello /
CMD ["/hello"]</pre>
<div class="blog_h2"><span class="graybg">多体系结构支持</span></div>
<p>该主题包含以下关注点：</p>
<ol>
<li>如何透明的依据体系结构，自动拉取镜像。这个需求可以通过镜像清单（Manifest）满足，清单包含同一功能的镜像（例如Alpine:3.10）对应到多个体系结构的版本。拉取镜像时，不管是什么平台，都使用alpine:3.10这个名字，Docker会自动拉取匹配体系结构的镜像</li>
<li>如何去构建其它体系结构的镜像，这个将是本节的主题</li>
</ol>
<div class="blog_h3"><span class="graybg">binfmt_misc</span></div>
<p>如果你使用了桌面版本的Docker，或者使用高于4.8+内核的Linux，你可以直接在x86_64的机器上，运行各种其它体系结构的镜像。</p>
<p>在Linux上启用Docker与QEMU集成的方式：</p>
<pre class="crayon-plain-tag">docker run --rm --privileged linuxkit/binfmt:v0.8

# 正常情况下，不会报错，并且
ls -1 /proc/sys/fs/binfmt_misc/qemu-*
# 出现：
# /proc/sys/fs/binfmt_misc/qemu-aarch64
# /proc/sys/fs/binfmt_misc/qemu-arm
# /proc/sys/fs/binfmt_misc/qemu-ppc64le
# /proc/sys/fs/binfmt_misc/qemu-riscv64
# /proc/sys/fs/binfmt_misc/qemu-s390x</pre>
<p>重启Docker守护进程后，可以看到默认Builder支持多种平台：</p>
<pre class="crayon-plain-tag">docker buildx ls

# BEFORE:
# NAME/NODE DRIVER/ENDPOINT STATUS  PLATFORMS
# default * docker                  
#   default default         running linux/amd64, linux/386


# AFTER:
NAME/NODE DRIVER/ENDPOINT STATUS  PLATFORMS
default * docker                  
  default default         running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6</pre>
<div class="blog_h3"><span class="graybg">buildx</span></div>
<p>这是一个Docker CLI插件，可以通过BuildKit进行Docker镜像的构建。使用buildx，你可以为不同体系结构构建镜像，并合并在一个镜像清单中，不需要作Dockerfile或源代码上的变动。</p>
<p>buildx把工作委托给builders：</p>
<pre class="crayon-plain-tag"># 列出现有的builders
docker buildx ls

# 创建本地builder:
docker buildx create --use --name local default
# 注意，docker驱动使用守护进程中的代理设置，但是docker-container不使用。此外由于builder容器
# 不是CLI创建的，因此~/.docker下设置的代理也不会变为容器的环境变量
# 目前支持创建builder时指定代理设置
docker buildx create 
                     # 使用哪种驱动
                     #   docker  使用编译到Docker守护进程中的BuildKit库，体验和原先的docker build相似
                     #   docker-container 在容器中启动BuildKit
                     #   kubernetes
                     --driver docker-container \
                     # 这些代理貌似也没什么价值，sh到builder容器中代理生效。但是Dockerfile中的命令，例如
                     # apk却不会使用这些代理，因此对构建过程没有价值
                     --driver-opt env.HTTP_PROXY=http://10.0.0.1:8088 \
                     --driver-opt env.HTTPS_PROXY=http://10.0.0.1:8088 \
                     --driver-opt network=host   \
                     --driver-opt '"env.NO_PROXY='$NO_PROXY'"'   \
  --use --name proxied default


# 添加远程builder：
docker context create arm --docker "host=tcp://10.0.0.90:2376"
docker context use arm
docker buildx use arm

# 切换使用的builder：
docker buildx use default</pre>
<p>要使用buildx发起镜像构建，执行命令：</p>
<pre class="crayon-plain-tag">docker buildx build .</pre>
<p>buildx将使用BuildKit引擎进行构建，不需要设置<pre class="crayon-plain-tag">DOCKER_BUILDKIT=1</pre>环境变量。 </p>
<p>buildx支持docker build的所有特性，包括19.03引入的输出配置、内联build缓存、指定目标platform。此外，buildx还支持manifest、分布式缓存、导出为OCI格式等docker build不支持的特性。</p>
<p>buildx可以<span style="background-color: #c0c0c0;">在不同配置下运行，每个配置称为driver</span>。默认使用编译到Docker守护进程中的BuildKit库，此驱动称为docker，该驱动使用你本地的Docker守护进程，并提供和docker build相似的体验。此外，你还可以使用docker-container驱动，它在容器中启动BuildKit。</p>
<p>驱动docker的输出，自动在docker images列表中可见。对于其它驱动，输出到何处需要<pre class="crayon-plain-tag">--output</pre>来指定。</p>
<p>通过创建新的builder实例，可以得到隔离的构建环境（不改变共享的Docker守护进程的状态），在CI中较为有用。 你甚至可以在远程Docker守护进程上创建多个builder，形成builder farm，并随意在这些builder之间切换。</p>
<p>Docker 19.03引入了类似Kubectl的context特性，可以为一个远程Docker守护进程的API端点提供一个名称。对于每个context，buildx会生成一个默认的builder实例。</p>
<div class="blog_h3"><span class="graybg">多体系结构镜像构建</span></div>
<pre class="crayon-plain-tag">docker buildx build --platform linux/amd64,linux/arm64 .</pre>
<div class="blog_h3"><span class="graybg">将buildx作为默认builder</span></div>
<p>执行命令：<pre class="crayon-plain-tag">docker buildx install</pre>，则docker build命令变为docker buildx的别名，这就意味着docker build自动使用docker buildx进行构建。 </p>
<p>执行<pre class="crayon-plain-tag">docker buildx uninstall</pre>移除别名。 </p>
<div class="blog_h1"><span class="graybg">Dockerfile</span></div>
<p>Docker读取Dockerfile中的指令以构建新的镜像，Dockerfile中的指令说明了镜像应该如何一步步的被构建。</p>
<div class="blog_h2"><span class="graybg">Dockerfile与镜像构建</span></div>
<p>执行<pre class="crayon-plain-tag">docker build</pre> 命令，可以从一个Dockerfile、一个上下文构建镜像。构建过程是由Docker守护程序（而不是客户端）执行的，客户端（docker build命令）做的第一件事情就是把整个上下文发送给守护程序。</p>
<p>所谓上下文，是指定PATH（宿主机本地目录）或者URL（Git存储库）中的文件集合。上下文会被递归的处理，即子目录被包含在上下文中。在大部分情况下，最好将空白目录作为上下文，其中仅包含一个Dockerfile和构建过程必备的文件。</p>
<p>要使用上下文中的文件，你必须编写特定的指令，例如COPY。你可以在上下文目录中添加一个<pre class="crayon-plain-tag">.dockerignore</pre> 来排除某些文件、目录以提高构建性能。</p>
<p>按照约定，在上下文根目录中的名为Dockerfile的文件被作为“Dockerfile”，但是你可以指定任意文件作为“Dockerfile”：</p>
<pre class="crayon-plain-tag">docker build -f /path/to/a/Dockerfile .</pre>
<p>你可以为正在构建的镜像指定仓库（Repository）:标签（tag），如果构建成功，镜像将被存放到相应的仓库:标签：</p>
<pre class="crayon-plain-tag">docker build -t gmemcc/myapp .
# 指定多个仓库/标签也是允许的
docker build -t gmemcc/myapp:1.0.2 -t gmemcc/myapp:latest .</pre>
<p>Docker守护程序会逐条的执行Dockerfile中的指令，如果必要，将指令的执行结果提交到正在构建的那个新的镜像中去。在最终输出镜像ID之前，守护程序会自动清理客户端发送的上下文。</p>
<p>需要注意的是，每条指令都是<span style="background-color: #c0c0c0;">独立的执行</span>的，因此<pre class="crayon-plain-tag">RUN cd /tmp</pre> 这样的指令不会对下一条指令的“工作目录”产生影响。</p>
<p>为了提高构建的性能，Docker会重用中间的（intermediate ）镜像（所谓缓存）。当使用缓存时，Docker会在控制台打印<pre class="crayon-plain-tag">Using cache</pre> 字样。</p>
<div class="blog_h2"><span class="graybg">Dockerfile格式</span></div>
<p>Dockerfile中只有注释、指令两类元素：</p>
<pre class="crayon-plain-tag"># Comment
INSTRUCTION arguments</pre>
<p>指令名称大小写不敏感，通常使用全大写。<span style="background-color: #c0c0c0;">第一条指令必须是FROM</span>，指定基础镜像。 以#开头的行通常被作为注释看待，除非它是合法的解析器指令（parser directive）。</p>
<div class="blog_h2"><span class="graybg">解析器指令</span></div>
<p>解析器指令（directive）影响Dockerfile中其它行的处理方式，该指令不会增加额外的层，也不会显示为构建步骤。</p>
<p>解析器指令的语法类似于一种特殊的注释：<pre class="crayon-plain-tag"># directive=value</pre> ，单个指令仅能被使用一次。一旦处理过任何注释、空行、指令（instruction），Docker就不再尝试分析任何解析器指令，因此<span style="background-color: #c0c0c0;">解析器指令必须位于Dockerfile的最开始处</span>。</p>
<p>解析器指令同样是大小写不敏感的，但是通常都使用全小写。在解析器指令之后，通常留有空白行。编写解析器指令时不得使用行连接符（\）</p>
<p>目前支持的解析器指令包括：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">解析器指令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>escape</td>
<td>
<p>设置Dockerfile中用来转义的前导字符，默认\。示例：<pre class="crayon-plain-tag"># escape=`</pre> </p>
<p>转义前导字符可以用于行内转义，也可以转义换行符。这允许一个指令（instruction）跨越多行</p>
<p>注意：对于<pre class="crayon-plain-tag">RUN</pre> 指令，转义仅仅会在行尾发生，即仅转义换行符</p>
<p>一般在<span style="background-color: #c0c0c0;">Windows上会将转义字符设置为`</span>，因为反斜杠是Windows的路径分隔符</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">变量替换</span></div>
<p>使用<pre class="crayon-plain-tag">ENV</pre> 指令可以声明环境变量，在Dockerfile中你可以声明Bash风格的变量替换：<pre class="crayon-plain-tag">$variable_name</pre> 或者<pre class="crayon-plain-tag">${variable_name}</pre> 。除了这两种基本格式以外，还可以使用某些Bash修饰符：</p>
<ol>
<li><pre class="crayon-plain-tag">${variable:-word}</pre> ，如果设置了环境变量variable，表达式的结果是$variable，否则是word</li>
<li><pre class="crayon-plain-tag">${variable:+word}</pre> ，如果设置了环境变量variable，表达式的结果是word，否则是空串</li>
</ol>
<p>以下指令<span style="background-color: #c0c0c0;">支持变量替换：ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL</span>。在1.4+，当ONBUILD与前述指令一起使用时，也支持环境变量。</p>
<p>注意：环境变量的值在同一个指令中保持不变，考虑下面的片断：</p>
<pre class="crayon-plain-tag">ENV abc=hello
ENV abc=bye def=$abc   # def值为hello而不是bye
ENV ghi=$abc           # ghi值为bye</pre>
<div class="blog_h2"><span class="graybg">.dockerignore</span></div>
<p>Docker客户端发送上下文到守护程序之前，会读取上下文根目录下名为.dockerignore的文件。如果此文件存在，则客户端会依据其中声明的规则来排除掉上下文中的部分文件或者目录。使用.dockerignore可以避免过多的、敏感的文件到守护程序，并被ADD/COPY命令复制到镜像中。</p>
<p>此文件中的每行是一个UNIX glob风格的匹配Pattern，上下文根目录被作为此Pattern的根目录，下面是一些示例：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">Pattern</td>
<td style="text-align: center;">排除</td>
</tr>
</thead>
<tbody>
<tr>
<td># comment</td>
<td>注释，被忽略</td>
</tr>
<tr>
<td>*/temp*</td>
<td>根目录的任意直接子目录中，任何以temp开头的目录或者文件</td>
</tr>
<tr>
<td>*/*/temp*</td>
<td>根目录的任意孙子目录中，任何以temp开头的目录或者文件</td>
</tr>
<tr>
<td>temp?</td>
<td>根目录值中任意以temp开头，后面附加一个任意字符的目录或者文件</td>
</tr>
<tr>
<td>**/*.go</td>
<td>任意以.go结尾的文件。**匹配0-N个目录</td>
</tr>
<tr>
<td>*.md<br /> !README.md</td>
<td>根目录中任何.md文件，除了README.md，!用于为排除指定例外</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">FROM指令</span></div>
<p>指令格式：</p>
<pre class="crayon-plain-tag">FROM &lt;image&gt;
FROM &lt;image&gt;:&lt;tag&gt;
FROM &lt;image&gt;@&lt;digest&gt;

# --platform  如果image是多平台的镜像，则此参数选择该镜像的特定平台版本
#             默认自动选择匹配构建目标平台的版本

# AS 为该build stage命名，名字可以被后续的stage引用
FROM [--platform=&lt;platform&gt;] &lt;image&gt; [AS &lt;name&gt;]</pre>
<p>用于指定正在构建镜像的Base镜像：</p>
<ol>
<li>该指令必须是Dockerfile中第一个非注释指令</li>
<li>该指令可以在一个Dockerfile中出现多次，用于构建多个镜像</li>
<li>tag、digest可选， 如果不指定，自动使用latest </li>
</ol>
<div class="blog_h3"><span class="graybg">多Stage构建</span></div>
<p>一个Dockerfile中可以包含多个FROM指令，这叫多Stage构建。下面是一个例子：</p>
<pre class="crayon-plain-tag">ARG ARCH

# 第一个stage，构建出二进制文件
FROM golang:1.13 as builder
WORKDIR /workspace
RUN CGO_ENABLED=0 GOOS=linux GOARCH=$ARCH GO111MODULE=on go build -a -o pause pause.go

# 第二个stage，构建出镜像
FROM docker.gmem.cc/tcnp/alpine-${ARCH}:3.11
WORKDIR /
#    用名字来引用前面的stage，复制出其中的文件
COPY --from=builder /workspace/pause .
ENTRYPOINT ["/pause"]</pre>
<div class="blog_h3"><span class="graybg">使用变量</span></div>
<p>如上面的例子所示，FROM中可以使用在<span style="background-color: #c0c0c0;"><strong>第一个FROM指令之前</strong>出现的ARG中定义的</span>构建时变量。</p>
<div class="blog_h2"><span class="graybg">MAINTAINER指令</span></div>
<p>指令格式：<pre class="crayon-plain-tag">MAINTAINER &lt;name&gt;</pre> 。用于指定生成的镜像的Author字段</p>
<div class="blog_h2"><span class="graybg">RUN指令</span></div>
<p>该指令具有两种格式：</p>
<pre class="crayon-plain-tag"># shell格式，command在Shell中运行，默认/bin/sh -c或者 cmd /S /C
RUN &lt;command&gt;
RUN ["executable", "param1", "param2"]

# 示例：
RUN ["/bin/bash", "-c", "echo hello"]</pre>
<p>该指令在当前intermediate镜像之上新建一层，并在其中执行任意命令，然后提交结果。提交结果后形成的新intermediate镜像被下一个指令使用。</p>
<p>运行RUN指令时触发分层（Layering）符合Docker的核心理念——提交（Commit）操作想对廉价，并且可以从镜像历史的任意位置（任意一层）创建容器。分层也是构建过程中镜像缓存的基础。</p>
<p>使用exec格式可以避免Shell字符串相关的陷阱，也可以在Base镜像中没有/bin/sh程序的情况下执行命令。使用exec格式时需要注意：</p>
<ol>
<li>由于exec格式不调用Shell，因此也不会发生变量替换：<br />
<pre class="crayon-plain-tag"># $HOME不会被替换
RUN [ "echo", "$HOME" ]
# $HOME会被替换，因为虽然是exec格式，但是它调用了sh
RUN [ "sh", "-c", "echo $HOME" ] </pre>
</li>
<li>exec指令的参数必须是规范化的JSON数组，所以：
<ol>
<li>字符串必须使用双引号包围</li>
<li>字符串中的反斜杠必须JavaScript语法转义，即<pre class="crayon-plain-tag">\\</pre>  </li>
</ol>
</li>
</ol>
<p>使用shell格式时，你可以使用<pre class="crayon-plain-tag">\</pre> 让命令跨越多行。</p>
<p>RUN指令导致的镜像缓存不会自动失效，要禁用缓存，可以在构建时指定<pre class="crayon-plain-tag">--no-cache</pre>选项。ADD指令可以导致RUN的缓存失效。</p>
<p>可以用SHELL指令，设置shell格式的RUN指令所使用的Shell程序，其它几个具有exec格式/shell格式区分的指令，也受到SHELL指令的影响。</p>
<div class="blog_h2"><span class="graybg">CMD指令</span></div>
<p>该指令具有三种格式：</p>
<pre class="crayon-plain-tag"># exec格式，推荐的格式
CMD ["executable","param1","param2"]
# 用作ENTRYPOINT的默认参数
CMD ["param1","param2"]
# shell格式
CMD command param1 param2</pre>
<p>Dockerfile中<span style="background-color: #c0c0c0;">仅能包含一个CMD指令</span>， 如果指定了多个CMD指令则仅最后一个有效。该指令的主要意图是为执行容器（executing container）提供默认值（defaults），这些默认值可以包含一个可执行文件，与ENTRYPOINT指令联用时，则仅仅包含执行选项。</p>
<p>使用CMD指令时要注意：</p>
<ol>
<li>CMD不会在构建阶段做任何事情</li>
<li>如果用CMD为ENTRYPOINT提供默认参数，则这两个指令的参数均为规范化的JSON数组</li>
<li>exec、shell格式调用的注意点，参考RUN指令</li>
</ol>
<p>如果使用了CMD指令，并且运行容器时没有指定需要执行的命令，则CMD中的命令会被执行。要<span style="background-color: #c0c0c0;">让容器每次都运行同一个程序</span>，应当联合使用ENTRYPOINT、CMD。</p>
<div class="blog_h2"><span class="graybg">LABEL指令</span></div>
<p>指令格式：<pre class="crayon-plain-tag">LABEL &lt;key&gt;=&lt;value&gt; &lt;key&gt;=&lt;value&gt; &lt;key&gt;=&lt;value&gt; ...</pre> </p>
<p>该指令为镜像添加元数据（metadata），每个标签是一个键值对。如果需要为镜像添加多个标签，最好在单个LABEL指令中完成。下面是一些示例：</p>
<pre class="crayon-plain-tag"># 如果键/值中包含空格，可以用引号包围
LABEL "Author Name"="Alex Wong"
# 可以使用反斜杠跨行
LABEL description="Hello \
There !"</pre>
<p>镜像会继承来自FROM镜像的标签，如果当前镜像的某个标签的键与FROM镜像冲突，则当前的<span style="background-color: #c0c0c0;">覆盖FROM</span>的。 </p>
<p>使用<pre class="crayon-plain-tag">docker inspect</pre> 命令可以查看一个镜像的所有标签。</p>
<div class="blog_h2"><span class="graybg">EXPOSE指令</span></div>
<p>指令格式：<pre class="crayon-plain-tag">EXPOSE &lt;port&gt; [&lt;port&gt;...]</pre> </p>
<p>该指令声明容器在运行时侦听的端口。该指令并不会让容器和宿主机之间发生端口映射，要真正映射（发布）端口，必须在创建容器时使用选项：</p>
<ol>
<li>使用-p参数运行镜像，指定发布的端口范围</li>
<li>使用-P参数，发布所有EXPOSE声明的端口</li>
</ol>
<div class="blog_h2"><span class="graybg">ENV指令</span></div>
<p>该指令支持两种格式：</p>
<pre class="crayon-plain-tag"># 为单个变量（key）设置单个值（value）。第一个空格之后的全部内容被作为值看待，即使其中包含空格、引号
ENV &lt;key&gt; &lt;value&gt;
# 允许设置多个环境变量
ENV &lt;key&gt;=&lt;value&gt; ...</pre>
<p>设置<span style="background-color: #c0c0c0;">构建过程、运行期间均可见的环境变量</span>，这些环境变量对任何后代Dockerfile都可用。</p>
<p>部分Dockerfile指令或者指令的某种形式，支持引用环境变量，语法和Bash一致。</p>
<p>在运行期间，可以使用docker inspect查看环境变量。在命令行中，可以使用<pre class="crayon-plain-tag">--env &lt;key&gt;=&lt;value&gt;</pre>覆盖环境变量设置。</p>
<div class="blog_h2"><span class="graybg">ADD指令</span></div>
<p> 该指令支持两种格式：</p>
<pre class="crayon-plain-tag"># src是上下文中的目录或者远程URI；dest为镜像中的目录
ADD &lt;src&gt;... &lt;dest&gt;
# 用于路径中包含空格的情况
ADD ["&lt;src&gt;",... "&lt;dest&gt;"]</pre>
<p>拷贝本地目录、文件或者远程文件URI，将它们加入到容器文件系统的dest位置。注意以下几点：</p>
<ol>
<li>可以指定多个src，src中可以包含*、?等<span style="background-color: #c0c0c0;">通配符</span></li>
<li>如果src是目录、文件，必须指定<span style="background-color: #c0c0c0;">相对于上下文根的相对路径</span>，src目录/文件必须位于上下文内部。/something、../something均非法</li>
<li>当src是目录时，其内部所有内容，包括文件系统元数据都被拷贝。但是目录本身不被拷贝</li>
<li>当src是一个本地压缩文件时，它被解压为一个目录，然后拷贝到dest</li>
<li>当src是本地目录（包括解压后的压缩文件）时，覆盖到dest的行为类似于tar -x</li>
<li>当src是远程URI时：
<ol>
<li>如果dest以/结尾，则文件名从URI中推定，保存为&lt;dest&gt;/&lt;filename&gt;</li>
<li>如果dest不以/结尾，则文件保存为dest</li>
</ol>
</li>
<li>dest指定容器中的绝对路径。如果使用相对路径，则相对于WORKDIR</li>
<li>当dest以/结尾时，被看作目录，否则看作一般文件。如果dest不存在，会被自动创建</li>
<li>容器中所有的新文件，以UID=GID=0创建</li>
<li>如果src为远程URI，则dest的模式被设置为600。如果通过HTTP协议获取远程文件，则Last-Modified头用于设置dest中文件的mtime</li>
<li>mtime不会影响缓存判断</li>
<li>当通过stdin读取Dockerfile并构建时：<pre class="crayon-plain-tag">docker build - &lt; somefile</pre> ，由于没有构建上下文，因此ADD指令必须使用基于URI的src。如果somefile是一个压缩包（tar.gz），则压缩包中根目录被作为构建上下文，而根目录中须有Dockerfile文件</li>
<li>由于ADD不支持身份认证，因此远程URI需要身份验证时，你必须使用RUN wget/curl代替ADD</li>
<li>如果src的内容发生变化，ADD会导致所有后续指令对应的Layer缓存失效</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag"># 拷贝上下文根目录中所有hom开头的文件到镜像的/mydir/目录下
ADD hom* /mydir/
# 拷贝上下文目录中home.txt之类的文件，e可以是任何单字符
ADD hom?.txt /mydir/

# 拷贝到相对路径 $WORKDIR/relativeDir/下
ADD test relativeDir/</pre>
<div class="blog_h2"><span class="graybg">COPY指令</span></div>
<p>该指令支持两种格式：</p>
<pre class="crayon-plain-tag">COPY &lt;src&gt;... &lt;dest&gt;
# 用于路径中包含空格的情况
COPY ["&lt;src&gt;",... "&lt;dest&gt;"]</pre>
<p>拷贝文件或者目录到容器文件系统中。 注意点类似于ADD，但是COPY不理解远程URI也不处理压缩文件。</p>
<div class="blog_h2"><span class="graybg">ENTRYPOINT指令</span></div>
<p>该指令支持两种格式：</p>
<pre class="crayon-plain-tag"># 推荐的exec格式，参数以JSON数组传递。该方式不会调用Shell命令，因此不会发生参数替换等行为
ENTRYPOINT ["executable", "param1", "param2"]
# shell格式，阻止传参。可以使用环境变量。容器运行时调用/bin/sh -c "command param1 param2"
ENTRYPOINT command param1 param2</pre>
<p>使用入口点，可以如同使用可执行文件那样运行一个镜像。docker run时，镜像名后面的所有参数，都<span style="background-color: #c0c0c0;">作为参数传递给ENTRYPOINT指定的命令/可执行文件</span>，并且覆盖CMD中指定的默认参数。当镜像名和可执行文件名一致的时候，dock run看起来就好像在执行一个文件，而不是运行镜像。</p>
<p><span style="background-color: #c0c0c0;">shell格式的调用，阻止CMD指定的默认参数和dock run指定的参数</span>。但是这种格式下可执行文件将作为/bin/sh -c的子命令运行，这导致可执行文件的PID不是1并且不能接收UNIX信号。你的可执行文件将无法接收docker stop发送来的SIGTERM信号。</p>
<p>如果该指令使用多次，则仅仅最后一个被使用。</p>
<p>你可以使用<pre class="crayon-plain-tag">--entrypoint</pre>选项覆盖ENTRYPOINT，但是只能指定执行的程序：</p>
<pre class="crayon-plain-tag"># 以命令bash作为入口点，覆盖默认的入口点，将--version作为bash的参数
docker run -it --rm  --entrypoint bash docker.gmem.cc/maven:3.5.2 --version</pre>
<div class="blog_h3"><span class="graybg">exec格式入口点示例</span></div>
<p>使用该格式，可以方便的设置容器执行的<span style="background-color: #c0c0c0;">默认命令</span>及其<span style="background-color: #c0c0c0;">稳定的默认参数</span>：<pre class="crayon-plain-tag">ENTRYPOINT ["top", "-b"]</pre> </p>
<p>然后，可以联用CMD指令，设置可<span style="background-color: #c0c0c0;">覆盖的默认参数</span>：<pre class="crayon-plain-tag">CMD ["-c"]</pre> </p>
<p>下面的例子，在前台（PID为1）运行Apache服务器：</p>
<pre class="crayon-plain-tag">FROM debian:stable
RUN apt-get update &amp;&amp; apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
# -D FOREGROUND 不去fork出子进程
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]</pre>
<p>你可以使用任何脚本作为入口点可执行程序的启动脚本，为了确保底层可执行文件能够接收UNIX信号，必须使用exec命令替换进程：</p>
<pre class="crayon-plain-tag">ENTRYPOINT ["/usr/bin/start-postgres.sh"]</pre><br />
<pre class="crayon-plain-tag">#!/bin/bash
set -e
# 如果第一个参数是postgress，则启动postgress服务
if [ "$1" = 'postgres' ]; then
    # 初始化，改变目录所有权
    chown -R postgres "$PGDATA"
    # 初始化数据库
    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi
    # 使用exec、管理员权限运行postgres。exec确保postgres进程PID=1，能够接收UNIX信号
    exec gosu postgres "$@"
fi
# 否则，运行参数指定的程序
exec "$@"</pre>
<p>如果需要进行清理工作，可以在入口点脚本中编写trap语句：</p>
<pre class="crayon-plain-tag"># 捕获多种信号，然后执行引号中的脚本，停止Apache服务
trap "stopping apache /usr/sbin/apachectl stop" HUP INT QUIT TERM
# 在后台启动Apache服务，这种服务无法接收信号
usr/sbin/apachectl start
echo "Press ENTER to exit"
read
# 回车后，也可以停止Apache服务
echo "stopping apache"
/usr/sbin/apachectl stop</pre>
<div class="blog_h3"><span class="graybg">shell格式入口点示例</span></div>
<p>这种方式指定的入口点，将在Shell中（默认 /bin/sh -c） 执行指定的程序，能够进行变量替换。该方式会<span style="background-color: #c0c0c0;">忽略CMD指令和docker run image 后面的参数</span>。</p>
<p>为了确保长时间运行的入口点程序能够正确接收docker stop发来的UNIX信号，应当使用exec来执行目标程序，例如：<pre class="crayon-plain-tag">ENTRYPOINT exec top -b</pre>  </p>
<div class="blog_h2"><span class="graybg">VOLUME指令</span></div>
<p>指令格式：</p>
<pre class="crayon-plain-tag">VOLUME ["/mount/point"]
VOLUME  /mount/point1 /mount/point2</pre>
<p>在镜像中创建一个挂载点，并将其标记为“承载来自宿主机、其它容器的外部卷”。</p>
<p>在构建阶段，你可以往挂载点写入数据，<span style="background-color: #c0c0c0;">然后再声明</span>VOLUME指令，这样数据会持久化在镜像中：</p>
<pre class="crayon-plain-tag">RUN echo "Hello Docker!" &gt; /mount/point/greetings
# 后声明VOLUME
VOLUME /mount/point
# 再此之后对/mount/point进行任何改动，都会被丢弃</pre>
<p>在docker run时， 容器会在/mount/point挂载新的卷，并且把greetings文件拷贝到此卷中。</p>
<div class="blog_h2"><span class="graybg">USER指令</span></div>
<p>指令格式：<pre class="crayon-plain-tag">USER username-or-uid</pre> ，指定RUN,CMD,ENTRYPOINT指令以什么身份运行。</p>
<p>在我机器上尝试MySQL镜像时，设置USER为mysql，生效的ID为999，而mysql用户的真实ID是118。直接设置USER为118，没有问题。</p>
<div class="blog_h2"><span class="graybg">WORKDIR指令</span></div>
<p>指令格式：<pre class="crayon-plain-tag">WORKDIR /path/to/workdir</pre> </p>
<p>指定RUN, CMD, ENTRYPOINT, COPY,ADD指令的工作目录，你可以多次使用该指令，并且可以使用相对（于上一个WORKDIR）路径。示例：</p>
<pre class="crayon-plain-tag">ENV PREFIX /usr/local
# 可以使用环境变量
WORKDIR $PREFIX/bin
# 下面的指令导致工作目录变为/usr/local/bin/python
WORKDIR python</pre>
<div class="blog_h2"><span class="graybg">ARG指令</span></div>
<p>指令格式：<pre class="crayon-plain-tag">ARG &lt;name&gt;[=&lt;default value&gt;] #该指令可以声明多次，也可以指定一个默认值</pre>  </p>
<p>该指令定义一个<span style="background-color: #c0c0c0;">构建时变量</span>。变量的值可以通过<pre class="crayon-plain-tag">docker build --build-arg &lt;varname&gt;=&lt;value&gt;</pre> 指定、覆盖。如果用户通过build-arg传递没有通过ARG声明的变量，构建会失败。</p>
<p>ARG定义的变量，对Dockerfile<span style="background-color: #c0c0c0;">当前行之后的指令</span>生效。ARG变量<span style="background-color: #c0c0c0;">不会持久化</span>到镜像中。</p>
<p>使用ENV、ARG都可以向RUN指令传递变量，如果这两个指令声明<span style="background-color: #c0c0c0;">同名的变量，则ENV总是覆盖ARG</span>。</p>
<p>以下ARG是Docker预定义的，不需要声明即可使用：http_proxy、https_proxy、ftp_proxy、no_proxy以及这4个变量的全大写版本。</p>
<div class="blog_h3"><span class="graybg">示例</span></div>
<pre class="crayon-plain-tag">ARG JAR_FILE
ADD target/$JAR_FILE /app.jar </pre>
<div class="blog_h3"><span class="graybg">对缓存的影响</span></div>
<p>如果ARG的值在下一次构建时发生了修改，则第一次使用它定义的变量的那个指令对应的Layer缓存失效。</p>
<div class="blog_h2"><span class="graybg">ONBUILD指令</span></div>
<p>指令格式：<pre class="crayon-plain-tag">ONBUILD [INSTRUCTION]</pre> </p>
<p>为镜像添加一个触发器指令（ trigger instruction），该指令会在<span style="background-color: #c0c0c0;">当前镜像被作为其它构建的Base镜像时执行</span>。ONBUILD指定的<span style="background-color: #99cc00;">指令</span>会在子镜像构建时、<span style="background-color: #c0c0c0;">在子镜像的构建上下文中</span>立即执行，就好像把该<span style="background-color: #99cc00;">指令</span>直接插入到FROM指令后面一样。</p>
<p>所有构建指令都可以通过ONBUILD注册为触发器。</p>
<p>当你制作一个专用于被扩展的Base镜像ONBUILD很有用。举例来说，假设你构建了一个Pyhton的应用编译环境（Base镜像），它要求被构建的Python源码被放置在特定的目录，并在之后调用编译脚本。你无法在构建Base镜像的时候使用ADD、RUN完成前述的工作，因为Base镜像的构建者并不知道源码在哪——每个具体应用程序的源码位置都不同。这时，ONBUILD可以帮助你：</p>
<ol>
<li>构建Base镜像时，当遇到ONBUILD时，Docker在镜像元数据中添加一条触发器数据。ONBUILD中的指令不会影响Base镜像的构建</li>
<li>在Base镜像构建完毕后，所有触发器的列表（对应多个ONBUILD）被存放到镜像的元数据清单（manifest）中，以OnBuild为Key。你可以对Base镜像执行docker inspect查看触发器列表</li>
<li>构建子镜像时，执行FROM指令时，ONBUILD触发器会按照它们在Base镜像中的声明顺序，依次执行，指有它们全部执行成功，子镜像才会继续构建，否则在FROM指令处失败</li>
<li>子镜像构建完毕后，ONBUILD触发器从子镜像的元数据中移除，这意味着<span style="background-color: #c0c0c0;">孙子镜像不会感知到ONBUILD</span>触发器</li>
</ol>
<p>下面是一个具体的例子：</p>
<pre class="crayon-plain-tag"># 将子镜像的构建上下文拷贝到临时容器的特定位置
ONBUILD ADD . /app/src
# 构建Python应用程序，结果的结果固化在子镜像中
ONBUILD RUN /usr/local/bin/python-build --dir /app/src</pre>
<p>构建子镜像时，只需要把Python程序源码放在上下文目录中，构建完毕后，子镜像的容器就直接可以使用与<span style="background-color: #c0c0c0;">子镜像同时构建的Python程序</span>了。 </p>
<p>使用ONBUILD时需要注意：</p>
<ol>
<li>不支持ONBUILD ONBUILD...</li>
<li>不支持ONBUILD FROM或者ONBUILD MAINTAINER</li>
</ol>
<div class="blog_h2"><span class="graybg">STOPSIGNAL指令</span></div>
<p>指令格式：<pre class="crayon-plain-tag">STOPSIGNAL signal</pre> ，指定为了让容器退出时，向其发送的信号。支持信号名称或者数字，例如SIGKILL、9。</p>
<div class="blog_h2"><span class="graybg">HEALTHCHECK指令</span></div>
<p>该指令支持两种格式：</p>
<pre class="crayon-plain-tag"># 在容器内运行一个命令以检测容器的健康状况
# command 要么是一个Shell命令字符串，要么是一个JSON数组
HEALTHCHECK [OPTIONS] CMD command
# 在CMD之前，可以指定以下选项：
--interval=DURATION   # 默认30s，第一次运行健康检测，于容器启动后DURATION秒后，以后每隔DURATION秒检测一次
--timeout=DURATION    # 默认30s，如果健康检测命令超过DURATION秒没有完成，则认为执行失败
--retries=N           # 默认3， 健康检测命令连续失败的最大次数，超过此次数，认定容器处于unhealthy状态

# 禁用任何从Base镜像继承来的健康检测指令
HEALTHCHECK NONE</pre>
<p>该指令告知Docker，如何确认容器仍然在正常工作。正常工作不仅仅要求进程在运行，还要求特定于具体应用的检测可以通过（例如对于Web应用，能够正常处理HTTP登录请求）。</p>
<p>当指定了该指令后，在normal status之外，还多了一个health status。该状态的初始值为starting，当健康检测通过后，变为healthy；当检测不通过、或者若干次检测失败后，变为unhealthy。</p>
<p>command的退出状态用于判断容器是否健康：0 表示健康；1表示不健康；2为暂不使用的保留值。command的输出到stdout/stderr的信息，可以通过docker inspect命令查看到。</p>
<p>每个Dockerfile仅支持一个HEALTHCHECK指令，如果指定了多个，只有最后一个生效。</p>
<div class="blog_h2"><span class="graybg">SHELL指令</span></div>
<p>指令格式：<pre class="crayon-plain-tag">SHELL ["executable", "parameters"]</pre> </p>
<p>用于覆盖那些使用shell格式的指令所使用的默认Shell：</p>
<ol>
<li>Linux下默认Shell为<pre class="crayon-plain-tag">["/bin/sh", "-c"]</pre> </li>
<li>Windows下默认Shell为<pre class="crayon-plain-tag">["cmd", "/S", "/C"]</pre> </li>
</ol>
<p>在Windows平台上该指令很有用，因为Windows提供了两个常用却完全不同的Shell：cmd和powershell。</p>
<p>SHELL指令可以出现多次，每次均覆盖之前的取值。</p>
<div class="blog_h2"><span class="graybg">多阶段构建</span></div>
<p>你可以在Dockerfile中使用多个FROM语句，每个FROM触发一个新的构建阶段（Stage）。你可以选择性的把某些构建从一个阶段拷贝到另外一个，而把任何不需要引入最终镜像的文件丢弃 —— 例如构建工具、依赖包。</p>
<p>下面是一个例子：</p>
<pre class="crayon-plain-tag">FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# --from表示从前面的stage拷贝
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]</pre>
<p>这个例子的第一阶段使用Go SDK构建源码，第二阶段则将第一阶段中的文件拷贝过来。</p>
<p>除了使用序号，你还可以通过名称来引用Stage：</p>
<div class="blog_h3"><span class="graybg">命名Stage</span></div>
<pre class="crayon-plain-tag">FROM golang:1.7.3 as builder
...
FROM alpine:latest 
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .</pre>
<div class="blog_h3"><span class="graybg">中止构建</span></div>
<p>你可以仅仅执行一部分Stage，然后就停止构建：</p>
<pre class="crayon-plain-tag"># 执行到builder这个stage即停止
docker build --target builder -t alexellis2/href-counter:latest .</pre>
<div class="blog_h3"><span class="graybg">外部镜像作为Stage</span></div>
<p>任何一个镜像都可以作为Stage使用，从中拷贝文件：</p>
<pre class="crayon-plain-tag">COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf</pre>
<div class="blog_h2"><span class="graybg">最佳实践</span></div>
<p>为了编写易用、有效的Dockerfile，Docker官方发布了若干条最佳实践。如果你要构建官方镜像，则必须遵守这些实践。</p>
<div class="blog_h3"><span class="graybg">朝生暮死的容器</span></div>
<p>容器的生命周期应该尽可能的短暂，创建、启动、停止、删除应当消耗最小化的时间。这意味着你应该尽可能的通过Dockerfile把内容放在镜像内。</p>
<div class="blog_h3"><span class="graybg">使用.dockerignore文件</span></div>
<p>大部分情况下，可以把Dockerfile放置在空白的目录中，后续仅在此目录中存放构建镜像时所必须的文件。 </p>
<p>如果目录中包含一些你需要排除出构建过程的目录/文件，可以编写一个.dockerignore文件，其格式类似于 .gitignore。</p>
<div class="blog_h3"><span class="graybg">避免安装不必要的包</span></div>
<p>尽可能少的安装Linux软件包，这样可以降低镜像的大小、构建过程的耗时</p>
<div class="blog_h3"><span class="graybg">一个容器一个进程</span></div>
<p>大部分情况下，你应该在单个容器中运行仅单个程序（例如Web服务、DB服务）。将应用程序解耦到不同容器中可以更好的实现容器重用、水平扩展。</p>
<p>当一个程序依赖于另外一个时，可以使用容器链接（container linking）。</p>
<div class="blog_h3"><span class="graybg">最小化层数</span></div>
<p>应当十分谨慎的考虑Dockerfile使用的层数（Number of layers），在Dockerfile可读性（可维护性）和最小化层数之间寻求平衡。</p>
<div class="blog_h3"><span class="graybg">排序命令行参数</span></div>
<p>尽可能的按照字典序对参数（特别是apt安装的软件包参数）进行排序，这样可以避免后续维护者添加重复的安装包。</p>
<div class="blog_h3"><span class="graybg">构建缓存</span></div>
<p>在构建镜像时，Docker会遍历你的Dockerfile的指令，检查完这些指令后，它会到缓存中寻找已经构建的镜像，而不是构建“重复”镜像。要避免使用缓存，可以在docker build时指定<pre class="crayon-plain-tag">--no-cache=true</pre> 参数。</p>
<p>如果使用缓存，你必须明白它是如何寻找“重复”镜像的：</p>
<ol>
<li>选取与当前Dockfile使用同一Base镜像的缓存镜像列表，遍历此列表，如果某个条目的构建指令与当前Dockerfile完全一致，则该条目可能是“重复镜像”</li>
<li>对于ADD、COPY指令，目标文件集合被检查并计算Checksum（不包含修改时间、最后访问时间信息），如果某个缓存镜像条目对应的ADD、COPY指令操控的文件集合的Checksum与前面的Checksum一致，则该条目匹配“重复”镜像</li>
</ol>
<p>当找不到匹配的镜像时，Docker将从头开始，构建一个新镜像。</p>
<div class="blog_h3"><span class="graybg">指令相关的建议</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 10%; text-align: center;">指令</td>
<td style="text-align: center;">最佳实践</td>
</tr>
</thead>
<tbody>
<tr>
<td>FROM</td>
<td>如果可能，使用Docker官方仓库作为基本镜像的来源，推荐使用debian:latest，因为此镜像作为一个完整发行版，一直并很好的控制并保持最小化（目前150MB）</td>
</tr>
<tr>
<td>LABEL</td>
<td>
<p>可以为镜像添加标签，以便更好的按照项目来组织镜像、记录License信息、辅助自动化。对于每个标签，以LABEL指令开头，后面跟随一个或者多个键值对信息。下面是一些示例：</p>
<pre class="crayon-plain-tag"># 在同一行中声明多个标签
LABEL cc.gmem.version="0.0.1-beta" cc.gmem.release-date="2015-02-12"
# 除了使用引号包围含有空格的字符串以外，还可以使用转义的方式：
LABEL vendor=Gmem\ Studio</pre>
</td>
</tr>
<tr>
<td>RUN</td>
<td>
<p>为了可读性、可维护性，应该把复杂的RUN语句编写在多行中，并用反斜杠分隔
<p><em><strong>apt-get</strong></em></p>
<p>大部分RUN指令都是调用apt-get来安装软件包的。注意避免使用upgrade、dist-upgrade子命令，因为某些基本的软件包不能在非特权容器中升级。</p>
<p>应当总是同时使用update子命令和install子命令，例如：</p>
<pre class="crayon-plain-tag">RUN apt-get update &amp;&amp; apt-get install pkg-bar</pre>
<p>因为单独使用RUN apt-get update指令，会导致软件包元信息驻留Docker缓存，以后的构建可能不更新软件包元信息。当然，你也可以指定软件包版本，这也可能避免上述缓存问题</p>
<p>一个RUN指令规范例子如下：</p>
<p><pre class="crayon-plain-tag"># 在同一个指令中完成update和install
RUN apt-get update &amp;&amp; apt-get install -y \
    # 按字典序排列软件包，便于维护
    aufs-tools \
    build-essential \
    curl \
    dpkg-sig \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \ # 指定软件包版本
 &amp;&amp; rm -rf /var/lib/apt/lists/*  
    # 清理apt缓存，减少镜像尺寸。
    # 注：Docker官方提供的Debian/Ubuntu镜像自动运行apt-get clean，因此不需要这里的rm命令</pre>
</td>
</tr>
<tr>
<td>CMD</td>
<td>
<p>该指令用于运行镜像中包含的软件，调用格式一般为：<pre class="crayon-plain-tag">CMD ["executable", "param1", "param2"…]</pre> </p>
<p>对于那些作为服务的软件，应该让它在前端执行（而不是在fork出的子进程中执行），例如运行Apache时应该使用指令<pre class="crayon-plain-tag">CMD ["apache2","-DFOREGROUND"</pre> </p>
<p>对于大部分其它软件，CMD指令应该给出一个交互式的Shell，例如<pre class="crayon-plain-tag">CMD ["perl", "-de0"]</pre> 、<pre class="crayon-plain-tag">CMD ["python"]</pre> </p>
</td>
</tr>
<tr>
<td>EXPOSE</td>
<td>
<p>该指令声明容器用来监听外部连接的端口，你应该使用约定俗成的端口：</p>
<p><pre class="crayon-plain-tag"># 暴露一个端口范围
EXPOSE 7000-8000
# 暴露一个端口
EXPOSE 8080 </pre>
</td>
</tr>
<tr>
<td>ENV</td>
<td>
<p>可以使用该指令更新PATH环境变量的值，以简化软件的调用。例如：<pre class="crayon-plain-tag">ENV PATH /usr/local/nginx/bin:$PATH</pre> 可以让Nginx通过简单的指令<pre class="crayon-plain-tag">CMD [“nginx”]</pre> 运行。</p>
<p>某些软件需要环境变量的设置，亦可通过该指令完成</p>
</td>
</tr>
<tr>
<td>ADD<br />COPY</td>
<td>
<p>两者功能类似，COPY应该更优先使用，因为它比ADD简单直观。COPY仅支持将本地文件拷贝到容器内这样的基本操作，而ADD则支持一些高级特性：</p>
<ol>
<li>解压本地Tar到容器中</li>
<li>远程URL支持</li>
</ol>
<p>最常见的需要使用ADD的场景是解压文件到镜像中：<pre class="crayon-plain-tag">ADD rootfs.tar.xz /.</pre> </p>
<p>如果在Dockerfile中存在多个步骤需要复制文件，不要把它们合并到单个COPY指令中。这样做的原因还是缓存，分开COPY可以减少不必要的缓存失效</p>
</td>
</tr>
<tr>
<td>ENTRYPOINT</td>
<td>
<p>该指令最佳应用场景是设置镜像的main命令，让容器像一个可执行文件那样运行。你可以配合使用CMD指令，来指定main命令的默认选项：</p>
<pre class="crayon-plain-tag">ENTRYPOINT ["s3cmd"]
CMD ["--help"]</pre>
<p>这样，你就可以运行镜像，而不指定需要执行的命令：</p>
<pre class="crayon-plain-tag"># 这里的s3cmd是镜像名，而不是s3cmd命令
docker run s3cmd
# 效果上等同于docker run s3cmd  s3cmd --help

# 你也可以传递任何其它选项
docker run s3cmd ls s3://gmem</pre>
<p>由于镜像名、命令名相同， 省略了其中之一的效果，就好像docker run在直接执行一个命令</p>
<p>也可以将ENTRYPOINT设置为一个脚本文件的路径：<pre class="crayon-plain-tag">ENTRYPOINT ["/docker-entrypoint.sh"]</pre> ，此脚本中的<pre class="crayon-plain-tag">$@</pre> 对应docker run imgname后面的整个参数数组</p>
</td>
</tr>
<tr>
<td>VOLUME</td>
<td>使用该指令暴露所有数据库的存储区域、配置文件的存储、以及容器创建的文件/目录，总之，所有容易变化的文件系统部分，都应该使用该指令暴露</td>
</tr>
<tr>
<td>USER</td>
<td>
<p>如果容器运行的服务不需要特权，则应该使用该指令指定一个非root用户，但是注意要在Dockerfile中预先添加这样的用户，例如：</p>
<pre class="crayon-plain-tag">RUN groupadd -r postgres &amp;&amp; useradd -r -g postgres postgres</pre>
<p>注意：自动分配的UID/GID在多次的镜像构建之间并不保持唯一 ，因此你最好手工指定UID/GID</p>
<p>避免安装/使用sudo，因为其不确定的TTY、信号转发行为会导致一些问题。如果必须使用类似于sudo的功能，可以安装<a href="https://github.com/tianon/gosu">gosu</a></p>
</td>
</tr>
<tr>
<td>WORKDIR</td>
<td>为了简洁、明确，使用WORKDIR指令时，应该总是使用绝对路径。应该使用WORKDIR来代替任何<pre class="crayon-plain-tag">RUN cd … &amp;&amp; do-sth</pre> 风格的指令</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">存储驱动</span></div>
<p>要有效利用Docker的存储机制，首先需要搞清楚它是如何构建、存储镜像的，然后要理解镜像是如何被容器使用的。</p>
<div class="blog_h2"><span class="graybg">镜像与层</span></div>
<p>一个镜像文件由一系列层层叠加的、只读的层（Layers）构成，这些层共同组成了镜像的根文件系统。Docker的存储驱动负责管理这些层，对外提供单一的文件系统视图。</p>
<p>当你创建一个容器时，Docker会在镜像的层叠之上，添加一个薄的、可读写的<span style="background-color: #c0c0c0;">容器层</span>（container layer）。容器运行过程中对文件系统的任何更改（增加、修改、删除文件），都持久化到容器层中。</p>
<p>所有镜像、容器的层都位于本地文件系统中，由存储驱动管理。在Linux下，默认存储目录为/var/lib/docker/。</p>
<p>下图是基于Ubuntu:15.04的容器的文件系统层叠示意图：</p>
<p><img class="aligncenter size-full wp-image-13721" src="https://blog.gmem.cc/wp-content/uploads/2015/10/container-layers.jpg" alt="container-layers" width="675" height="469" /></p>
<div class="blog_h3"><span class="graybg">内容寻址存储</span></div>
<p>Docker从1.10开始，引入了一个新的内容寻址存储（Content addressable storage，CAS）模型。该模型提供了在磁盘上寻址镜像、层数据的全新方法。在此之前，镜像、层数据使用随机的UUID来引用和存储，CAS则使用内容哈希值（content hash）来引用镜像、层数据。</p>
<p>CAS增强了安全性，内置了防止ID冲突的机制，并且能在pull、push、load、save操作后保证数据完整性。它也提供了更好的层共享机制——允许很多镜像自由的共享层，即时这些层来自不同的构建。</p>
<p>使用CAS后，所有层的ID均变成基于其内容的哈希值。但是，容器的ID仍然是随机的UUID。</p>
<p>这一变化导致，从老版本的Docker升级到1.10后：</p>
<ol>
<li>既有镜像需要迁移：由于层ID的生成规则发生变化，老版本Docker从仓库Pull下来的镜像的ID必须重新生成。新版本的Docker守护程序在初次运行时会自动执行这一迁移操作。迁移操作完毕后，所有镜像、标签具有新的secure IDs。如果本地镜像非常多，迁移操作可能消耗很长的时间才能完成，这期间Docker守护程序无法响应外部请求</li>
</ol>
<div class="blog_h2"><span class="graybg">容器与层</span></div>
<p>从存储角度来看，镜像与容器的主要区别就在于后者的Layer栈顶部包含一个薄的可读写层。当容器创建后，该读写层一同创建；当容器删除后，该读写层也被删除；容器运行时，所有对文件系统的变更都落到这个读写层中。</p>
<p>由于一个镜像的所有容器具有自己的可读写层，因而它们能够安全的共享底层的镜像。Docker的存储驱动负责管理所有的层（不管是镜像还是容器的），如何管理取决于具体的驱动，但是两个关键技术是通用的：1、可层叠镜像层（stackable image layers）；2、copy-on-write</p>
<div class="blog_h2"><span class="graybg">copy-on-write策略</span></div>
<p>这个策略在软件系统中很常见。例如在操作系统中，两个使用相同数据块的进程，可以安全共享数据块的单份拷贝。只有当其中一个进程需要对共享数据进行修改（而另外一个进程不进行修改）时，才需要共享数据的额外拷贝。</p>
<p>Docker同时对镜像、容器使用此CoW策略，以便节约存储空间并缩短容器启动时间。CoW依赖于存储驱动的支持，不是所有驱动支持CoW。</p>
<div class="blog_h3"><span class="graybg">共享：更小的镜像</span></div>
<p>执行pull/push子命令时，Docker客户端会报告其操作的Layer：</p>
<pre class="crayon-plain-tag">docker pull ubuntu:15.04
# 9502adfba7f1: Pull complete 
# 4332ffb06e4b: Pull complete 
# 2f937cc07b5f: Pull complete 
# a3ed95caeb02: Pull complete 
# Digest: sha256:2fb27e433b3ecccea2a14e794875b086711f5d49953ef173d8a03e8707f1510f
# Status: Downloaded newer image for ubuntu:15.04</pre>
<p>可以看到，拉取Ubuntu镜像时，实际上下载了4个Layer，它们共同组成了完整的系统镜像。</p>
<p>如果使用AUFS驱动，在1.10版本之前，Layer以其ID为目录名，存放在本地存储的子目录/var/lib/docker/aufs/layers中。</p>
<p>不管Docker引擎的版本是多少，相同的Layer都会被共享，而不是重新拉取。</p>
<div class="blog_h3"><span class="graybg">拷贝：高效的容器</span></div>
<p>前面提到，对容器文件系统的所有修改都写在一个薄的顶部层中，来自镜像的层都是只读的，这意味着多个容器可以共享一个镜像。</p>
<p>当容器修改了一个文件后，Docker利用存储引擎来执行CoW策略，具体细节取决于引擎。对于AUFS、OverlayFS这两种存储引擎来说，CoW的大概步骤如下：</p>
<ol>
<li>从顶层向下，逐层寻找，定位到被修改的文件</li>
<li>将找到的文件拷贝（Copy-up）到顶部的可写层</li>
<li>修改位于可写层中的文件副本</li>
</ol>
<p>Copy-up操作可能带来重大的性能影响，影响程度取决于存储驱动。但是<span style="background-color: #c0c0c0;">过大的文件、过多的层、过深的目录树</span>都会加重影响程度。不过Copy-up操作对一个文件至多发生一次。</p>
<p>CoW让容器共享镜像，这一方面让容器本身的尺寸很小，另一方面则让容器高效的创建、执行，因为不需要牵涉到太多的I/O操作。</p>
<div class="blog_h2"><span class="graybg">数据卷与存储驱动</span></div>
<p>当容器被删除后，所有没有存储在数据卷（Data volume）中的数据都会被删除。数据卷是宿主机上的一个目录或者文件，它被直接挂载到容器的目录树中。</p>
<p>多个容器可以共享数据卷，但是要注意并发修改的问题。</p>
<p>数据卷不受存储驱动的控制，对其进行的I/O操作绕过存储驱动，直接由宿主机执行，其速度和宿主机上普通I/O操作一样快。</p>
<div class="blog_h2"><span class="graybg">存储驱动选型</span></div>
<div class="blog_h3"><span class="graybg">可拔插存储驱动架构</span></div>
<p>为了基于实际运行环境选择最好的驱动，Docker设计了可拔插的存储驱动架构。存储驱动基于一个Linux文件系统或者卷管理器，它们可以自由的实现镜像/容器Layer的管理。</p>
<p>在决定使用哪种驱动后，你需要设置Docker守护程序的启动参数。修改/etc/default/docker中的DOCKER_OPTS，添加<pre class="crayon-plain-tag">--storage-driver=&lt;sd_name&gt;</pre> 选项。</p>
<p>注意一个<span style="background-color: #c0c0c0;">Docker守护程序同时只能使用一种存储驱动</span>。执行命令：</p>
<pre class="crayon-plain-tag">docker info | grep "Storage Driver"
# Storage Driver: aufs</pre>
<p>可以看到当前使用的存储驱动。上例中的aufs为存储驱动的名称，存储技术与存储驱动名称对照表如下： </p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">存储技术</td>
<td style="width: 15%; text-align: center;">存储驱动名称</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>OverlayFS</td>
<td>overlay<br />overlay2</td>
<td>
<p>内核的一部分，速度最快的UnionFS。支持页共享缓存，多个容器访问同一个文件时，可共享相同的页面缓存，因而其<span style="background-color: #c0c0c0;">内存利用效率很高</span></p>
<p>overlay2是overlay的升级版，在4.0内核之后，添加额外的特性，防止过多的索引节点（inode）消耗</p>
</td>
</tr>
<tr>
<td>AUFS</td>
<td>aufs</td>
<td>
<p>Ubuntu/Debian系统默认的存储驱动，历史悠久、稳定</p>
<p>允许容器之间共享公共库的内存，因此如果有<span style="background-color: #c0c0c0;">千百个相同的容器，其内存效率比较高</span></p>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>Btrfs</td>
<td>btrfs</td>
<td> </td>
</tr>
<tr>
<td>Device Mapper</td>
<td>devicemapper</td>
<td>
<p>Redhat/Fedora系统默认的存储驱动</p>
<p>Device Mapper是内核中支持LVM的通用设备映射机制，它为块设备驱动提供了一个模块化的内核架构</p>
<p>基于块设备而非文件系统，内置配额支持，其它驱动则不支持</p>
</td>
</tr>
<tr>
<td>VFS</td>
<td>vfs</td>
<td> </td>
</tr>
<tr>
<td>ZFS</td>
<td>zfs</td>
<td> </td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">共享存储与存储驱动</span></div>
<p>许多企业使用SAN、NAS之类的共享存储技术以提高性能和可靠性，或者实现Thin Provisioning（避免预分配过多的容量）、数据复制、压缩等高级特性。</p>
<p>Docker存储驱动、数据卷均可以在这些共享存储系统之上工作，但是Docker不能直接与底层的共享存储技术集成。你需要选择与共享存储技术匹配的存储驱动。</p>
<div class="blog_h3"><span class="graybg">存储驱动对比</span></div>
<p><img class="aligncenter size-large wp-image-13750" src="https://blog.gmem.cc/wp-content/uploads/2015/10/driver-pros-cons-1024x464.png" alt="driver-pros-cons" width="710" height="321" /></p>
<div class="blog_h3"><span class="graybg">Overlay与Overlay2</span></div>
<p>OverlayFS是目前使用比较广泛的层次文件系统，实现简单，<span style="background-color: #c0c0c0;">读写性能较好</span>，并且稳定。</p>
<p>与OverlayFS相关的存储驱动有两个：overlay、overlay2。前者的缺陷包括inode耗尽和commit performance，后者就是为了解决这两个问题而生，但是需要4.0或者更高版本的Linux内核。</p>
<div class="blog_h2"><span class="graybg">AUFS驱动</span></div>
<p>AUFS是第一个出现的Docker驱动，它非常稳定，在生产环境下有很多部署案例，并且社区支持很好。AUFS的优势包括：</p>
<ol>
<li>容器启动时间短</li>
<li>存储利用效率高</li>
<li>内存利用效率高</li>
</ol>
<p>对于PaaS或者其它需要<span style="background-color: #c0c0c0;">高密度容器实例</span>的应用场景，AUFS是很好的选择。但是由于CoW策略，第一次写文件操作可能带来很大的开销。</p>
<p>由于AUFS不在Linux内核主线中，因此某些发行版不会自带AUFS，需要手工下载和安装。</p>
<div class="blog_h3"><span class="graybg">安装与配置</span></div>
<p>要验证当前系统是否支持AUFS，可以执行命令：</p>
<pre class="crayon-plain-tag">cat /proc/filesystems | grep aufs</pre>
<p>如果上述命令没有输出，先确保你的内核版本高于3.13，并参考如下命令安装：</p>
<pre class="crayon-plain-tag">sudo apt-get install linux-image-extra-$(uname -r) linux-image-extra-virtual</pre>
<p>安装完毕后，修改Docker守护程序的参数：</p>
<pre class="crayon-plain-tag">sudo dockerd --storage-driver=aufs</pre>
<p>上面的参数修改不是持久化的，要永久的修改，需要打开Docker配置文件，添加：</p>
<pre class="crayon-plain-tag">DOCKER_OPTS="--storage-driver=aufs" </pre>
<div class="blog_h3"><span class="graybg">镜像分层与共享</span></div>
<p>AUFS是一种联合文件系统（UFS），它管理<span style="background-color: #c0c0c0;">单个Linux宿主机</span>上的多个目录，并将其叠加在一起，在单个挂载点形成统一的视图。与UnionFS、OverlayFS类似，AUFS是一种union mounting实现。AUFS管理的每个Linux宿主机目录称为联合挂载点（union mount point）或者分支（branch）。</p>
<p>AUFS的思想与Docker的Layer机制天然吻合——每个branch对应一个镜像/容器的Layer。Copy-up操作实质上就是在宿主机文件系统的不同目录之间复制文件。</p>
<div class="blog_h3"><span class="graybg">容器读写</span></div>
<p>由于AUFS在文件级别上操作，这意味着，即使在仅仅修改文件一小部分的情况下，CoW操作也需要复制整个文件。因此，当写入一个体积很大的文件时，AUFS会遭遇一次性的性能问题。</p>
<p>当删除文件时，AUFS在顶层Layer添加一个所谓without文件，该文件命名为<pre class="crayon-plain-tag">.wh.filename</pre> ，用来标记目标文件在容器中被删除。</p>
<div class="blog_h3"><span class="graybg">本地存储</span></div>
<p>使用AUFS时，镜像、容器的文件默认被存放到dockerd所在机器的/var/lib/docker/aufs/目录下 。</p>
<div class="blog_h1"><span class="graybg">网络配置</span></div>
<div class="blog_h2"><span class="graybg">默认网络</span></div>
<p>安装Docker之后，会自动创建bridge、none、host这三个网络，可以通过命令<pre class="crayon-plain-tag">docker network ls</pre> 查看。如果启动容器时不指定<pre class="crayon-plain-tag">--network</pre> 参数，默认使用bridge网络，bridge在宿主机网络栈中映射为docker0。none表示容器不连接到任何网络，其本地网络设备仅仅lo这个环回网卡。host网络则把容器添加到宿主机网络栈中，在容器中执行ifconfig你会看到输出与宿主机一致。</p>
<p>在三个默认网络中，通常你只会和bridge做交互。这些网络不能被删除，因为Docker本身需要使用。</p>
<p>你可以创建其它自定义网络，并在不需要的时候删除它们。</p>
<div class="blog_h2"><span class="graybg">自定义网络</span></div>
<p>为了更好的隔离容器，可以创建自定义网络。利用Docker提供的网络驱动，你可以创建桥接（Bridged）、重叠（Overlay）、MACVLAN等类型的网络。</p>
<p>自定义网络可以创建多个，每个容器也可以加入到多个网络中。容器仅仅能在网络内部而不能跨网络通信。当容器连接到多个网络时，其外网连接性由第一个（词法序）具有外部连接能力的网络提供。</p>
<div class="blog_h2"><span class="graybg">桥接网络</span></div>
<p>Linux内核支持虚拟网桥，可以连接多个网络接口，功能类似于交换机。</p>
<p>在自定义网络中，桥接是最简单的一种。添加到桥接网络的容器，必须位于<span style="background-color: #c0c0c0;">同一台宿主机上</span>。网络中的所有容器可以相互通信，但是这些容器不能访问外部网络。</p>
<p>与Docker0不同，自定义桥接网络不支持--link。但是你可以暴露/发布容器的端口，这样桥接网络的一部分可以被外部访问。<span style="background-color: #c0c0c0;">自定义桥接网络嵌入了DNS服务器</span>，容器之间可以通过名字访问。</p>
<p>当你需要基于单宿主机建立一个简单的网络时，可以考虑自定义桥接网络。</p>
<p>桥接网络的示例：</p>
<pre class="crayon-plain-tag">docker network create -d bridge --subnet=172.21.0.0/16 --gateway=172.21.0.99 local </pre>
<div class="blog_h3"><span class="graybg">docker_gwbridge</span></div>
<p>这个特殊的本地桥接网络，在以下两种情况下，由Docker自动创建：</p>
<ol>
<li>当你初始化或者加入到一个swarm时，Docker创建docker_gwbridge，以便跨主机的进行swarm节点之间的通信</li>
<li>如果容器所加入的任何网络都不具有外部连接性，Docker把容器连接到docker_gwbridge</li>
</ol>
<p>你可以提前手工创建docker_gwbridge网络，进行定制化配置。</p>
<p>当使用overlay网络时，docker_gwbridge总是存在。</p>
<div class="blog_h2"><span class="graybg">覆盖网络</span></div>
<p>利用Overlay网络可以跨越多台宿主机组网。</p>
<div class="blog_h3"><span class="graybg">基于swarm</span></div>
<p>在swarm模式下运行Docker引擎时，你可以在管理节点上创建overlay网络，这种网络不需要外部的key-value存储。</p>
<p>swarm可以让overlay网络仅仅对需要它以提供一个服务的swarm节点可用。当你创建一个使用overlay网络的服务时，管理节点会自动扩展overlay网络以覆盖运行服务的节点。</p>
<p>下面的命令示例如何在swarm管理节点创建overlay网络，并在服务中使用它：</p>
<pre class="crayon-plain-tag"># 创建一个overlay网络
docker network create --driver overlay --subnet 10.0.9.0/24 my-multi-host-network
# 创建一个Nginx服务，并把刚刚创建的网络扩展到运行Nginx服务的那些节点
docker service create --replicas 2 --network my-multi-host-network --name my-web nginx</pre>
<p>注意：这种overlay网络对docker run启动的容器是不可用的，目标容器必须是swarm模式服务的一部分。</p>
<p>基于swarm模式的overlay网络默认具有安全保证。swarm节点使用gossip协议来交换overlay网络的信息，并且使用GCM模式的AES算法加密gossip协议，管理节点默认每12小时更换密钥。</p>
<p>如果要加密容器通过overlay网络交换的信息，需要使用选项：</p>
<pre class="crayon-plain-tag">docker network create --opt encrypted --driver overlay  nw</pre>
<p>上述命令将自动为参与到overlay网络的节点创建IPSEC通道，这些通道也使用GCM模式的AES算法，每12小时更换密钥。 </p>
<div class="blog_h3"><span class="graybg">基于外部KV存储</span></div>
<p>此方式的overlay网络与swarm模式的overlay网络不兼容。主要用于需要考虑兼容性的场景。</p>
<p>当不在swarm模式下使用Docker引擎时，启用overlay网络依赖于外部key-value存储的支持，这些存储包括Consul、Etcd、ZooKeeper。你需要手工安装这些存储，并确保它们可以和Docker宿主机自由通信。</p>
<p>宿主机必须开启4789、7946端口。如果启用加密的overlay网络（--opt encrypted）则需要允许protocol50(ESP)流量。此外KV服务也需要暴露端口。</p>
<p>各宿主机上的Docker引擎守护程序，需要配置dockerd选项以支持overlay网络：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>--cluster-store=PROVIDER://URL</td>
<td>指名KV服务的位置</td>
</tr>
<tr>
<td>--cluster-advertise=HOST_IP|HOST_IFACE:PORT</td>
<td>指定用于集群的宿主机网络接口</td>
</tr>
<tr>
<td>--cluster-store-opt=KEY-VALUE OPTIONS</td>
<td>指定KV服务的配置项</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Macvlan网络</span></div>
<p>这种网络不同于overlay，可以把容器直接暴露到物理网络中。</p>
<p>首先，创建网络：</p>
<pre class="crayon-plain-tag"># 设置目标网卡为混杂模式
sudo ip link set virbr0 promisc on
# parent指定桥接到的宿主机网卡，以及子网信息
docker network create -d macvlan --subnet=10.0.0.0/8 --gateway=10.0.0.5  -o parent=virbr0 virbr0</pre>
<p>然后，运行容器： </p>
<pre class="crayon-plain-tag">docker run -it --rm --network=virbr0 --ip=10.0.0.100 ubuntu:14.04</pre>
<p>注意，如果不指定IP，则容器的IP会从当前网段的192.168.0.2开始分配，不管是否被占用。 </p>
<div class="blog_h2"><span class="graybg">网络别名</span></div>
<div class="blog_h3"><span class="graybg">--link与出站解析</span></div>
<p>连接到默认桥接网络，且以--link选项创建容器时：</p>
<ol>
<li>可以解析其它容器的名字为IP</li>
<li>可以为其它容器指定任意别名：<pre class="crayon-plain-tag">--link=CONTAINER-NAME:ALIAS</pre> </li>
<li>安全容器连接，即--icc=false</li>
<li>环境变量注入</li>
</ol>
<p>当使用自定义桥接网络时，上述特性默认启用，不需要额外的选项。并且你可以：</p>
<ol>
<li>基于网络内嵌的DNS服务器进行容器名到IP的解析</li>
<li>使用--link（仅用来）指定别名</li>
</ol>
<p>link选项的示例：</p>
<pre class="crayon-plain-tag"># 创建容器，连接到isolated_nw，并且当前容器访问container5时可以使用别名c5
docker run --network=isolated_nw -itd --name=container4 --link container5:c5 busybox
# 在连接到某个网络时，也可以指定link选项
docker network connect --link container5:foo local_alias container4</pre>
<div class="blog_h3"><span class="graybg">--network-alias</span></div>
<p>与--link不同 ，该选项可以指定当前容器在网络中的别名，网络中的其它容器都可以使用该别名：</p>
<pre class="crayon-plain-tag">docker run --network=isolated_nw -itd --name=container6 --network-alias app busybox</pre>
<p>多个容器可以声明相同的别名，这种情况下，其中一个容器（随机）会响应DNS解析。当该容器宕掉后，其它同名容器自动响应解析。这个特性可以用来实现简单的高可用性。</p>
<div class="blog_h2"><span class="graybg">自定义网络中的DNS</span></div>
<p>自定义网络中的容器的DNS查找行为与默认bridge网络不同，处于向后兼容的目的，后者的行为与旧版本保持一致。</p>
<p>自1.10版本开始，Docker守护程序嵌入了DNS服务，此服务支持基于容器link、name、net-alias配置的DNS查找。容器还可以通过--dns等选项来指定外部DNS服务器，当内嵌DNS服务器无法解析某个主机名时，会转给外部DNS处理。</p>
<div class="blog_h1"><span class="graybg">资源限制</span></div>
<p>默认情况下，容器不被施加资源限制，可以使用宿主机内核调度器允许的最大资源。Docker提供了控制内存、CPU、块I/O用量限制的方法。</p>
<div class="blog_h2"><span class="graybg">内存</span></div>
<p>你可以为容器施加硬性内存限制，确保容器不占用超过特定值的用户/系统内存。你也可以施加软性限制，允许容器按需使用内存，但当特定条件（例如内核检测到宿主机内存过低）发生时，限制容器内存占用。</p>
<p>内存限制通过docker run命令的选项给出，这些选项的值都是正整数，后面可以跟着b/k/m/g等单位：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-m    --memory</td>
<td>限制容器能够使用的最大内存量，最小值4m</td>
</tr>
<tr>
<td>--memory-swap</td>
<td>该容器可以被交换到磁盘的内存的量 </td>
</tr>
<tr>
<td>--memory-swappiness</td>
<td>0-100之间， 允许宿主机交换出容器使用的匿名页的百分比</td>
</tr>
<tr>
<td>--memory-reservation</td>
<td>指定一个小与-m的软限制，当Docker检测到宿主机存在内存争用、内存低下情况时，限制容器使用不超过此数值的内存 </td>
</tr>
<tr>
<td>--kernel-memory</td>
<td>限制容器能够使用的最大内核内存，最小值4m。注意内核内存不能被换出 </td>
</tr>
<tr>
<td>--oom-kill-disable</td>
<td>默认情况下内存溢出错误发生后，Docker会杀死容器中的进程，此选项禁用该默认行为 </td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">CPU</span></div>
<p>默认的，容器可以无限制的使用CPU时钟周期。Docker提供了若干选项，对容器的CPU使用进行限制。这些选项支持CFS调度器，自1.13开始实时调度器也支持某些选项。</p>
<div class="blog_h3"><span class="graybg">CFS相关选项</span></div>
<p>CFS是用于大部分Linux进程的调度器，Docker提供以下容器选项：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>--cpus</td>
<td>
<p>指定容器可以使用多少CPU资源。如果宿主机具有2个CPU，你可以设置该选项为1.5表示容器最多使用1个半CPU的运算能力</p>
<p>--cpus=1.5和配置--cpu-period="100000" --cpu-quota="150000"等价</p>
</td>
</tr>
<tr>
<td>--cpu-period</td>
<td>指定CFS调度周期数</td>
</tr>
<tr>
<td>--cpu-quota</td>
<td>指定CFS调度配额数</td>
</tr>
<tr>
<td>--cpuset-cpus</td>
<td>限制容器能够使用的CPU核心，0-3表示可以使用第1-4个CPU核心，1,3表示可以使用第二个、第四个</td>
</tr>
<tr>
<td>--cpu-shares</td>
<td>
<p>默认值1024，设置更大或者更小的值，可以调整容器可以访问的CPU时钟周期的相对权重</p>
<p>如果足够的空闲CPU周期存在，此选项不会限制容器的CPU使用</p>
<p>在Swarm模式下，该选项不会限制容器被调度</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">实时调度器相关选项</span></div>
<p>自1.13版本开始，Docker支持配置容器使用实时调度器。作为前提条件，宿主机内核选项CONFIG_RT_GROUP_SCHED必须开启。</p>
<p>要使用实时调度器运行容器，需要设置dockerd选项<pre class="crayon-plain-tag">--cpu-rt-runtime</pre> 来指定在某个运行周期内，为实时任务保留的毫秒数。对于默认的10000微秒周期，设置--cpu-rt-runtime=95000意味着至少保留5000微秒给非实时任务使用。</p>
<p>相关配置选项：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>--cap-add</td>
<td>授予容器CAP_SYS_NICE特性，这样容器可以提升进程的nice值、设置实时调度策略、设置CPU关联性（affinity）</td>
</tr>
<tr>
<td>--cpu-rt-runtime</td>
<td>在Docker守护程序的实时调度周期内，容器最多可以使用的微秒数</td>
</tr>
<tr>
<td>--ulimit</td>
<td>容器的最大实时优先级</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">多进程容器</span></div>
<p>典型的容器在启动时，仅仅会发动一个进程——例如Apache或者SSH守护进程。要在容器中运行多个服务，你可以编写脚本，或者使用进程管理工具。</p>
<p>Supervisor是一个流行的进程管理工具，使用它可以更加方便的控制、管理、重启容器中的进程。本章以一个例子来说明如何使用Supervisor来管理容器中的多个服务。</p>
<div class="blog_h2"><span class="graybg">Dockerfile</span></div>
<pre class="crayon-plain-tag"># 安装Supervisor和两个服务
RUN apt-get update &amp;&amp; apt-get install -y openssh-server apache2 supervisor
# 守护进程需要的目录
RUN mkdir -p /var/lock/apache2 /var/run/apache2 /var/run/sshd /var/log/supervisor
# 复制Supervisor配置文件到容器上下文
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# 暴露端口
EXPOSE 22 80
# 容器启动时执行supervisord
CMD ["/usr/bin/supervisord"]</pre>
<div class="blog_h2"><span class="graybg">Supervisor配置文件</span></div>
<p>该配置文件内容如下：</p>
<pre class="crayon-plain-tag">; 第一段，配置Supervisor本身
; nodaemon提示Supervisor以交互式运行，而不是守护式。这样可以确保它可以正常接收信号
[supervisord]
nodaemon=true

; 以下的每个段，分别定义一个需要被控制的服务
[program:sshd]
command=/usr/sbin/sshd -D

[program:apache2]
command=/bin/bash -c "source /etc/apache2/envvars &amp;&amp; exec /usr/sbin/apache2 -DFOREGROUND"</pre>
<div class="blog_h1"><span class="graybg">运行时度量</span></div>
<div class="blog_h2"><span class="graybg"> docker stats</span></div>
<p>该命令可以实时监控容器的CPU、内存、网络、块I/O资源的占用情况。</p>
<div class="blog_h2"><span class="graybg">控制组Cgroup</span></div>
<p>Linux容器所依赖的内核机制——控制组（Cgroups），不仅仅支持跟踪进程组，还暴露了度量CPU、内存、块I/O的接口。</p>
<p>控制组通过一个伪文件系统<pre class="crayon-plain-tag">/sys/fs/cgroup</pre> 暴露，每一个子目录对应了一种Cgroup层次（子系统）。某些老的系统挂载位置可能有不同，你可以执行<pre class="crayon-plain-tag">grep cgroup /proc/mounts</pre> 命令以查看。</p>
<p>在每个子系统内部，可以存在多级子目录。这些目录的最深处，会包含1-N个伪文件，其中包含了统计信息。</p>
<p>查看文件/proc/cgroups可以查看系统中已经支持的Cgroup层次。输出中包含Cgroup子系统名称、包含的组数量等信息。</p>
<p>查看文件/proc/$PID/cgroup可以查看某个进程所属的Cgroup。输出 / 表示没有划分到特定的Cgroup，/lxc/pumpkin可能意味着进程属于容器pumpkin的成员。</p>
<div class="blog_h3"><span class="graybg">内存度量</span></div>
<p>子系统memory提供内存的统计信息。由于memory控制组会增加一定的overhead，因此某些发行版默认情况下禁用了它。你可能需要添加内核参数：</p>
<pre class="crayon-plain-tag">cgroup_enable=memory swapaccount=1</pre>
<p>该子系统对应的伪文件是memory.stat。不包含total_前缀的数据项，与当前Cgroup中的进程有关，包含total_d前缀的数据项，则与当前Cgroup、所有子代Cgroup中的进程有关。</p>
<p>常用数据项列表如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">度量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>cache</td>
<td>使用的系统缓存量，这些缓存代表映射到块文件系统中的内存页。当你读写文件（open/write/read系统调用）、进行内存映射（mmap系统调用）、挂载tmpfs时，该数据项值增加</td>
</tr>
<tr>
<td>rss</td>
<td>没有映射到块文件系统的内存页，包括栈、堆、匿名内存映射</td>
</tr>
<tr>
<td>mapped_file</td>
<td>Cgroup中进程映射的内存的量</td>
</tr>
<tr>
<td>pgfault, pgmajfault</td>
<td>
<p>Cgroup中进程触发页错误（page fault）、页major fault的次数</p>
<p>当进程访问不存在（例如访问无效地址，这可能导致进程接收附带Segmentation fault消息的SIGSEGV信号进而被杀死）、或者被保护（进程读取当前已经被换出的页）的虚拟内存空间时，会触发页错误</p>
<p>Major错误在内核真实的从磁盘读取页时发生</p>
</td>
</tr>
<tr>
<td>swap</td>
<td>Cgroup中进程当前使用的交换文件大小</td>
</tr>
<tr>
<td>active_anon, inactive_anon</td>
<td>
<p>被内核识别为活动、非活动的匿名内存的量。所谓匿名内存是指没有和磁盘页关联的内存</p>
<p>页一开始的状态是活动的，内核会定期扫描内存，并把某些页标记为非活动。一旦这些页再次被访问，立即重新标记为活动。当内存不足需要交换出磁盘时，非活动页被交换出</p>
<p>rss = active_anon + inactive_anon - tmpfs </p>
</td>
</tr>
<tr>
<td>active_file, inactive_file</td>
<td>
<p>被内核识别为活动、非活动的非内存的量</p>
<p>cache = active_file + inactive_file + tmpfs</p>
</td>
</tr>
<tr>
<td>unevictable</td>
<td>不可交换出磁盘的内存用量。某些敏感信息，例如密钥，会被mlock保护，防止被交换到磁盘上</td>
</tr>
<tr>
<td>memory_limit, memsw_limit</td>
<td>不是真正的度量信息，而是应用到Cgroup的资源限制。前者为物理内存用量限制，后者为物理内存+Swap</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">CPU度量</span></div>
<p>该子系统对应的伪文件是cpuacct.stat，包含容器中进程累计的CPU使用信息。user、system分别表示用户空间、系统空间中代码消耗的时间。时间的单位为jiffies，在X86上通常为10ms。</p>
<div class="blog_h3"><span class="graybg">块I/O度量</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">度量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>blkio.sectors</td>
<td>Cgroup中进程读取/写入的512字节的扇区总数</td>
</tr>
<tr>
<td>blkio.io_service_bytes</td>
<td>Cgroup中进程读取/写入的字节数，每个设备包含4个数据，分别对应同步/异步的读/写</td>
</tr>
<tr>
<td>blkio.io_serviced</td>
<td>Cgroup中进程读取/写入操作次数，每个设备包含4个数据，分别对应同步/异步的读/写</td>
</tr>
<tr>
<td>blkio.io_queued</td>
<td>
<p>Cgroup中进程发起的、正在排队的I/O操作数量。如果Cgroup没有执行任何I/O操作，则计数为0，如果Cgroup正在执行IO操作，计数可能为0——例如正在空闲设备上执行纯同步操作</p>
<p>注意此计数是一个相对值。可以用来判断哪个容器在给IO系统施加压力</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">网络度量</span></div>
<p>Cgroup没有直接暴露网络度量信息。尽管内核可以计算出一个Cgroup中进程收发的网络包数量，但是意义不大。你可能需要基于网络接口的度量，因为lo接口上的流量没有太大价值。</p>
<p>单个Cgroup中的进程可以属于多个网络名字空间（ network namespace），多个网络名字空间意味着多个lo甚至eth0，这导致难以收集Cgroup的网络流量。</p>
<p>你可以基于iptables规则设置计数器，然后使用命令获取度量。</p>
<p>获取网络接口级别的度量信息是可能的，因为每个容器都关联了宿主机上的一个虚拟以太网接口。但是难以知道这些接口和容器的对应关系。</p>
<p>命令<pre class="crayon-plain-tag">ip netns exec</pre> 允许你在宿主机上，进入任意网络名字空间，执行任意命令。这意味着宿主机可以进入容器的网络名字空间。参考如下命令：</p>
<pre class="crayon-plain-tag"># $CID为容器ID，TASKS为容器下进程的PID列表
TASKS=/sys/fs/cgroup/devices/docker/$CID*/tasks
PID=$(head -n 1 $TASKS)
# 创建后面命令需要的符号连接
mkdir -p /var/run/netns
ln -sf /proc/$PID/ns/net /var/run/netns/$CID
# 在容器的网络名字空间下执行netstat命令
ip netns exec $CID netstat -i</pre>
<div class="blog_h2"><span class="graybg">容器所属的Cgroup</span></div>
<p>对于每一个容器，在每一个Cgroup子系统中均会创建一个与之对应的组。</p>
<p>如果使用最近版本的LXC工具，Cgroup名字为lxc/$container_name。</p>
<p>对于使用Cgroup的Docker容器，Cgroup路径为/sys/fs/cgroup/$subsystem/docker/$longid/，其中longid为容器完整的ID。</p>
<div class="blog_h1"><span class="graybg">Swarm</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>要使用Swarm模式，你可以安装1.12版本以上的Docker。Swarm模式用于管理Docker引擎的集群。你可以使用Docker CLI来创建Swarm、部署应用服务到Swarm、管理Swarm的行为。</p>
<div class="blog_h3"><span class="graybg">特性列表</span></div>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td>
<p><strong>集成到Docker引擎的集群管理功能</strong></p>
<p>你可以直接使用Docker CLI来管理Swarm，不需要额外的软件或者组件</p>
</td>
</tr>
<tr>
<td>
<p><strong>去中心化设计</strong></p>
<p>Docker引擎在运行时来处理节点角色的特殊化——Manager还是Worker，而不是在部署期间。你可以用单个镜像来部署整个Swarm</p>
</td>
</tr>
<tr>
<td>
<p><strong>声明式服务模型</strong></p>
<p>Docker引擎基于一种声明式的途径来定义你的应用栈中各种服务的期望状态。例如你可以声明式的描述由三个组件构成的应用：前端Web服务、消息队列服务、数据库</p>
</td>
</tr>
<tr>
<td>
<p><strong>扩容（Scaling）</strong></p>
<p>对于每一个服务，你可以声明你期望运行的任务数（Tasks）。当Scale up/down时Docker引擎会自动add/remove任务，以维持期望的状态</p>
</td>
</tr>
<tr>
<td>
<p><strong>期望状态协调（Desired state reconciliation）</strong></p>
<p>Swam管理节点会持续监控集群的状态，并且尽可能消除当前状态与期望状态之间的差别。例如，假设你定义一个运行容器10个实例的服务，而一台运行2个实例的宿主机宕机了，此时管理节点会自动创建两个实例代替之，并把实例分配给可用的Worker节点</p>
</td>
</tr>
<tr>
<td>
<p><strong>跨主机网络支持</strong></p>
<p>你可以为服务创建Overlay网络，管理节点会在初始化、更新应用程序时，自动为容器分配对应Overlay网络上的IP地址</p>
</td>
</tr>
<tr>
<td>
<p><strong>服务发现</strong></p>
<p>管理节点为Swarm中的每个服务分配唯一的DNS名称，你可以通过Swarm中内嵌的DNS进行服务查找</p>
</td>
</tr>
<tr>
<td>
<p><strong>负载均衡</strong></p>
<p>你可以选择暴露服务的端口给外部的负载均衡器。在内部，Swarm允许你指定如何在节点之间分发服务</p>
</td>
</tr>
<tr>
<td>
<p><strong>安全性</strong></p>
<p>Swarm中节点之间的通信基于TLS认证和加密。你可以使用自签名根证书</p>
</td>
</tr>
<tr>
<td>
<p><strong>滚动更新（Rolling updates）</strong></p>
<p>你可以增量的更新节点上的服务，Swarm允许控制不同节点集上服务部署的延迟。如果出现异常情况，你可以把一个Task回滚到上一个版本</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">关键概念</span></div>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td>
<p><strong>Swarm</strong></p>
<p>基于SwarmKit构建的、内嵌在Docker引擎中的集群管理和编排机制。参与到Swarm集群中的Docker引擎运行在Swarm模式。要切换到Swarm模式，你可以新建一个Swarm、或者加入一个既有的Swarm</p>
<p>Swarm也可以指代基于上述机制的Docker引擎（或者叫节点）集群，你在Swarm中部署服务。CLI和Docker API包含管理节点、部署和编排服务的命令</p>
<p>在普通模式下，你执行容器命令；在Swarm模式下，你编排服务。在同一个Docker引擎下，你可以同时运行独立容器、Swarm服务</p>
</td>
</tr>
<tr>
<td>
<p><strong>Node</strong></p>
<p>节点即参与到Swarm中的Docker引擎。你可以在单台物理机器上运行一个或者多个节点。通常生产环境下Swarm由跨越多台物理机器的节点组成</p>
<p>要部署应用到Swarm，你需要把服务定义（service definition）到管理节点。管理节点负责分发称为任务（Task）的工作单元给Worker节点</p>
<p>管理节点也负责执行服务编排、集群管理功能，以维持集群处于期望的状态。管理节点们会推举一个Leader节点来主导编排工作</p>
<p>Worker节点接收、执行管理节点派发的任务，默认情况下管理节点也像Worker节点一样运行服务，但是你可以将其配置为Manager-only节点。Worker节点上运行着一个代理（Agent），此代理负责报告分配给Worker的Task的状态到Manager，这样Manager就可以维持期望状态</p>
</td>
</tr>
<tr>
<td>
<p><strong>Service &amp; Task</strong></p>
<p>所谓Service，是关于需要在Worker节点上执行的Tasks的定义。服务是Swarm系统的核心结构，也是用户和Swarm交互的主要切入点</p>
<p>当定义服务时，你可以指定使用什么镜像，以及当运行容器时需要执行什么命令</p>
<p>在复制服务（ replicated services）模型下，Manager节点会基于你在期望状态中设置的Scale，分发一定数量的复制Task</p>
<p>对于全局服务（global services），Swarm在集群中每个可用节点上，运行此服务的单个Task</p>
<p>Task使用一个容器，并在其中执行特定的命令。Task是Swarm的原子调度单元。前面提到过，Swarm根据Service的Scale设定决定Task的数量并分发。分发的Task只能在某个节点上运行或者失败，而不能转移到其它节点</p>
</td>
</tr>
<tr>
<td>
<p><strong>负载均衡</strong></p>
<p>Swarm基于入口负载均衡（ingress load balancing ）暴露对外服务。你可以为服务配置PublishedPort，如果不指定Swarm可以自动分配30000-32767之间的端口</p>
<p>诸如云负载均衡器之类的外部组件，可以访问Swarm集群中<span style="background-color: #c0c0c0;">任意节点的PublishedPort</span>以使用服务，不管节点是否运行服务的Task。所有节点都会自动把入口连接路由到运行了Task的节点</p>
<p>Swarm基于内部负载均衡（internal load balancing）将请求分配给服务的实例，其依据是服务的DNS名称。Swarm内置的DNS组件会自动的给所有Service分配DNS条目</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">起步</span></div>
<div class="blog_h3"><span class="graybg">硬件和网络</span></div>
<p>准备三台宿主机，一台用作Manager，其它的用作Worker。Swarm中所有节点都必须能够访问Manager的IP地址。以下端口必须开启：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 60px; text-align: center;">端口</td>
<td style="width: 100px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>2377</td>
<td>TCP</td>
<td>此端口用于集群管理通信</td>
</tr>
<tr>
<td>7946</td>
<td>TCP/UDP</td>
<td>用于Overlay网络的流量传输</td>
</tr>
<tr>
<td>4789</td>
<td>UDP</td>
<td>用于容器入口路由网</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">创建Swarm</span></div>
<p>默认的，Swarm模式是禁用的。你可以创建新Swarm、加入既有Swarm，以使当前节点进入Swarm模式。</p>
<p>确保Docker引擎守护程序正在运行，然后登录到Manager节点，执行：</p>
<pre class="crayon-plain-tag">docker swarm init --advertise-addr 10.0.0.1</pre>
<p>这样当前节点就称为新建Swarm集群的管理节点了。管理节点使用通知地址（advertise address）来允许集群中其它节点访问Swarmkit API以及Overlay网络，因此IP地址10.0.0.1必须可以被所有其它节点访问到。如果宿主机具有单个IP地址，你可以不指定--advertise-addr，反之则必须指定。</p>
<p>执行docker info，可以看到当前Swarm的基本信息；执行docker node ls则可以看到集群中的节点列表。</p>
<div class="blog_h3"><span class="graybg">添加节点</span></div>
<p>首先在Manager节点上执行：</p>
<pre class="crayon-plain-tag"># 获取Worker的加入令牌
docker swarm join-token worker --quiet
# 输出  SWMTKN-1-5evgfnqh3xw67rtqucyq3cctxb929sqwfczv8s1gtz0cptpe5m-84evs0quy124fql5zcyxp7ppe

# 获取Manager的加入令牌
docker swarm join-token manager --quiet </pre>
<p>登录到用作Worker节点的宿主机，执行：</p>
<pre class="crayon-plain-tag">docker swarm join --token SWMTKN-1-...-... 10.0.0.1:2377</pre>
<p>作为工作节点加入时，swarm join子命令会执行以下操作：</p>
<ol>
<li>把目标节点的Docker引擎切换到Swarm模式</li>
<li>向管理节点请求一个TLS证书</li>
<li>基于宿主机名来命名节点</li>
<li>通过管理节点的通知地址、基于令牌加入目标节点到集群中</li>
<li>设置目标节点的可用性为Active，使之能够接收Task</li>
<li>扩展ingress overlay网络，使之覆盖当前节点</li>
</ol>
<p>作为管理节点加入时，执行的操作与上面类似。新的管理节点状态为Reachable，但是Swarm的Leader不变。</p>
<p>所谓令牌，是加入Swarm时需要的一个字符串，作为管理节点/工作节点加入时的令牌是不一样的。管理节点的令牌要特别注意保护。当发生以下情况下，考虑使用join-token子命令更改（rotate）令牌：</p>
<ol>
<li>令牌被意外泄漏，例如签入到版本控制系统</li>
<li>如果怀疑某个节点被入侵</li>
<li>如果期望禁止任何可能的新节点加入到Swarm</li>
<li>建议最多每6个月更换令牌</li>
</ol>
<p>下面的命令示例如何更换工作节点令牌：</p>
<pre class="crayon-plain-tag">docker swarm join-token  --rotate worker </pre>
<div class="blog_h3"><span class="graybg">查看节点</span></div>
<p>在管理节点上运行<pre class="crayon-plain-tag">docker node ls</pre> 可以查看Swarm中所有节点的基本信息。</p>
<p>输出的AVAILABILITY列表示节点的可用性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">可用性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Active</td>
<td>调度器可以分配任务给此节点</td>
</tr>
<tr>
<td>Pause</td>
<td>调度去不会分配新任务给此节点，但是既有的任务会保持运行</td>
</tr>
<tr>
<td>Drain</td>
<td>调度去不会分配新任务给此节点，并且节点上正在运行的任务会被停止，重新分配到Active节点上运行</td>
</tr>
</tbody>
</table>
<p>输出的MANAGER STATUS表示节点参与Raft consensus的情况：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">管理节点状态</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Leader</td>
<td>此节点是主管理节点，负责所有Swarm管理工作、编排决定</td>
</tr>
<tr>
<td>Reachable</td>
<td>此节点参与Raft consensus，如果当前的主管理节点宕机，此节点有望晋升</td>
</tr>
<tr>
<td>Unavailable</td>
<td>此节点无法和其它管理节点通信。这种情况下你要么重新添加一个管理节点，要么提升一个工作节点为管理节点</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">部署服务</span></div>
<p>所谓服务就是在特定镜像上执行的命令，而任务即服务的实例，任务的数量即服务的replicas个数。</p>
<p>在管理节点上运行：</p>
<pre class="crayon-plain-tag">docker service create --replicas 4 --name ping ubuntu:14.04 ping 10.0.0.1</pre>
<p>即可创建在Ubuntu 14.04上运行ping命令的服务，这个服务有4个实例（Task） 。</p>
<p>执行下面的命令可以查看任务执行的概要信息：</p>
<pre class="crayon-plain-tag">docker service ls

# ID            NAME  REPLICAS  IMAGE         COMMAND
# 48qbjfwd8u8r  ping  1/4       ubuntu:14.04  ping 10.0.0.1</pre>
<p>如果想让服务对Swarm外部可见，你需要暴露特定的端口。</p>
<p>你可以设置服务的环境变量、工作目录、运行身份：</p>
<pre class="crayon-plain-tag">docker service create --name ping --env MYVAR=myvalue --workdir /tmp  --user my_user  ...</pre>
<p>使用--secret选项，可以授予服务对Docker管理的Secret的访问权。</p>
<div class="blog_h3"><span class="graybg">查看服务</span></div>
<p>可以执行以下命令查看服务执行的详细信息：</p>
<pre class="crayon-plain-tag">docker service inspect --pretty  ping
docker service ps  ping</pre>
<div class="blog_h3"><span class="graybg">扩容服务</span></div>
<p>在管理节点上执行下面的命令，可以动态的修改服务的任务数量：</p>
<pre class="crayon-plain-tag">docker service scale ping=1</pre>
<div class="blog_h3"><span class="graybg">删除服务</span></div>
<p>在管理节点上执行下面的命令可以删除服务：</p>
<pre class="crayon-plain-tag">docker service rm ping</pre>
<p>注意，尽管服务被删除，执行Task的容器可能还需要一段时间执行清理工作</p>
<div class="blog_h3"><span class="graybg">滚动更新</span></div>
<p>所谓滚动更新，是指逐步的更新服务的每个实例。下面的例子演示如何从Redis 3.0.6滚动更新到Redis 3.0.7。</p>
<p>首先创建服务：</p>
<pre class="crayon-plain-tag">docker service create  --replicas 3   --name redis   --update-delay 10s  --update-parallelism 1 redis:3.0.6</pre>
<p>选项--update-delay定义了更新一个/一组任务的延迟时间，你可以使用10m30s这样的形式。 默认情况下是一个接着一个的更新，要每次更新多个Task可以使用选项--update-parallelism。</p>
<p>默认情况下，当正在更新的任务状态变为RUNNING后，调度器会调度下一个任务的更新，此步骤一直执行直到所有任务都被更新，如果更新过程中任何一个任务返回FAILED状态，则调度器暂停更新。选项--update-failure-action可以改变此行为。</p>
<p>执行下面的命令把Redis版本更改为3.0.7：</p>
<pre class="crayon-plain-tag">docker service update --image redis:3.0.7 redis

# 更新步骤：
# 1、停止第1个任务
# 2、调度此任务的更新操作
# 3、启动被更新过的任务
# 4、如果新任务返回RUNNING，继续更新下一个任务
# 5、如果新任务返回FAILED，暂停更新</pre>
<p>要重启被暂停的更新，可以执行：</p>
<pre class="crayon-plain-tag">docker service update redis</pre>
<div class="blog_h3"><span class="graybg">回滚服务</span></div>
<p>如果更新后的服务工作不正常，你可以回滚到前一个版本：</p>
<pre class="crayon-plain-tag"># --rollback 回滚服务
# --update-delay 0s 立即回滚
docker service update --rollback --update-delay 0s my_web</pre>
<div class="blog_h3"><span class="graybg">Overlay网络</span></div>
<p>当Swarm中的几个服务需要相互通信时，可以使用Overlay网络。</p>
<p>首先，在Swarm模式下的管理节点上创建Overlay网络：</p>
<pre class="crayon-plain-tag">docker network create --driver overlay my-network</pre>
<p>这样，所有管理节点都可以访问该Overlay网络了。创建服务：</p>
<pre class="crayon-plain-tag">docker service create  --replicas 3 --network my-network --name my-web nginx</pre>
<p>这样，所有运行my-web服务的Task的节点，都被Overlay网络覆盖。</p>
<div class="blog_h3"><span class="graybg">Drain节点</span></div>
<p>前面例子中的Worker节点，其可用性（availability）都是ACTIVE。Manager节点可以向ACTIVE派发任务。</p>
<p>某些时候（例如需要维护节点硬件），需要把可用性设置为DRAIN。DRAIN阻止管理节点派发新的任务，并且，停止DRAIN节点上正在运行的任务，在可用的ACTIVE节点上启动对应数量的任务副本。</p>
<p>执行下面的命令可以查看节点可用性：</p>
<pre class="crayon-plain-tag">docker node ls

# ID                           HOSTNAME  STATUS  AVAILABILITY  MANAGER STATUS
# 5imye5fakqgdd7jg6erkize8a    coreos    Ready   Active        
# ap7gwtam1jjqvp3h4arco6vdz *  Zircon    Ready   Active        Leader
# bfini24kw83rzrh2e5cb33g8f    Jade      Ready   Active</pre>
<p>执行下面的命令可以设置DRAIN：</p>
<pre class="crayon-plain-tag"># 可以使用HOSTNAME或者ID
docker node update --availability drain  coreos</pre>
<p>维护完成后，重新设置为ACTIVE：</p>
<pre class="crayon-plain-tag">docker node update --availability active coreos</pre>
<div class="blog_h3"><span class="graybg">入口路由网</span></div>
<p>为了让外部资源能够轻松的访问Swarm中的服务，Docker引擎提供了方便的端口暴露机制。所有Swarm节点均参与到一个入口路由网（Ingress Routing Mesh），此路由网允许Swarm中的任意节点接收某个服务暴露端口的请求——甚至在节点没有运行此服务的任务的情况下。路由网负责把对暴露端口的请求路由到活动的服务容器中。</p>
<p>要正常使用入口路由网，需要确保TCP/UDP端口7946、UDP端口4789开放。当然，Swarm服务暴露的端口也需要被外部资源（例如负载均衡器）正常访问。</p>
<div class="blog_h3"><span class="graybg">暴露端口</span></div>
<p>要暴露端口，可以在创建服务时使用<pre class="crayon-plain-tag">--publish PUBLISHED-PORT:TARGET-PORT</pre> 选项。其中TARGET-PORT是运行服务实例（Task）的容器监听的端口，PUBLISHED-PORT则是Swarm对外暴露的端口。示例：</p>
<pre class="crayon-plain-tag"># 暴露端口8080，此端口自动转发给my-web服务运行节点上的80端口
docker service create --name my-web  --publish 8080:80 --replicas 2  nginx</pre>
<p>对于已经存在的服务，可以在更新时指定<pre class="crayon-plain-tag">--publish-add PUBLISHED-PORT:TARGET-PORT</pre> 选项来新增暴露端口。</p>
<p>暴露的端口，默认是TCP端口，可以使用以下格式指定TCP或者UDP端口：</p>
<pre class="crayon-plain-tag"># TCP
--publish 53:53
--publish 53:53/tcp
# TCP和UDP
--publish 53:53/tcp -p 53:53/udp 
# UDP
--publish 53:53/udp</pre>
<div class="blog_h3"><span class="graybg">直接暴露端口</span></div>
<p>使用入口路由网的端口暴露机制，可能不满足应用需求。你可能需要根据应用程序状态来决定如何路由请求，或者你需要对路由处理过程进行完全的控制。</p>
<p>要直接暴露服务所在运行的节点上的端口，可以使用<pre class="crayon-plain-tag">--publish mode=host</pre> 选项。如果不和<pre class="crayon-plain-tag">--mode=global</pre> 联用该选项，将难以知晓哪些节点运行了服务。</p>
<div class="blog_h3"><span class="graybg">更新镜像</span></div>
<p>在1.13版本之后，在服务创建之后你可以使用<pre class="crayon-plain-tag">service update --image</pre> 更改服务基于的镜像。较老的版本则只能重新创建服务。</p>
<p>每个镜像Tag对应了一个摘要（Digest），就像Git的Hash一样。某些标签，例如latest，其指向的摘要会改变。当你运行service update --image时，管理节点根据Tag到Docker Hub或者本地私服查询。如果：</p>
<ol>
<li>管理节点能够把Tag解析为Digest，则指示工作节点使用Digest对应的镜像来重新部署任务
<ol>
<li>如果工作节点已经缓存了此Digest对应的镜像，则使用之</li>
<li>否则，从Docker Hub或者私服拉取镜像
<ol>
<li>如果拉取成功，基于新镜像部署任务</li>
<li>否则，服务在工作节点上部署失败。Docker会尝试重新部署任务（可能在其它节点）</li>
</ol>
</li>
</ol>
</li>
<li>管理节点不能够正常解析Tag，则指示工作节点使用Tag对应的镜像重新部署任务
<ol>
<li>如果工作节点已经缓存了此Tag对应的镜像，则使用之</li>
<li>否则，从Docker Hub或者私服拉取镜像
<ol>
<li>如果拉取成功，基于新镜像部署任务</li>
<li>否则，服务在工作节点上部署失败。Docker会尝试重新部署任务（可能在其它节点）</li>
</ol>
</li>
</ol>
</li>
</ol>
<div class="blog_h3"><span class="graybg">切换服务模式和Scale</span></div>
<p>可以使用--replicas选项设置服务的需要的任务数：</p>
<pre class="crayon-plain-tag">docker service create --name my_web --replicas 3 nginx</pre>
<p>可以使用--mode选项设置服务是全局模式还是复制模式：</p>
<pre class="crayon-plain-tag">docker service create --name myservice --mode global alpine top</pre>
<div class="blog_h3"><span class="graybg">为服务预留内存和CPU</span></div>
<p>选项<pre class="crayon-plain-tag">--reserve-memory</pre> 和<pre class="crayon-plain-tag">--reserve-cpu</pre> 用于声明服务要求的空闲内存、CPU数量。如果节点不满足条件，则不会被分配任务。</p>
<div class="blog_h3"><span class="graybg">挂载配置</span></div>
<p>你可以为Swarm服务创建两种类型的挂载：volume、bind。要创建挂载，指定--mount选项，如果不指定--type，默认类型为volume。</p>
<p>卷挂载（volume）是当运行任务的容器被移除后，仍然存在的存储。要利用既有的卷时，通常使用此类型的挂载：</p>
<pre class="crayon-plain-tag">docker service create  --mount src=VOLUME-NAME,dst=CONTAINER-PATH --name myservice IMAGE

# 在部署期间（调度器分发任务后、启动容器前）创建一个卷挂载：
docker service create --mount type=volume,src=VOLUME-NAME,dst=CONTAINER-PATH,volume-driver=DRIVER,\
    volume-opt=KEY0=VALUE0,volume-opt=KEY1=VALUE1  --name myservice IMAGE</pre>
<p>绑定挂载（Bind） 映射到运行服务容器的宿主机。在Swarm初始化容器前，宿主机路径必须存在。示例：</p>
<pre class="crayon-plain-tag"># 挂载为读写
docker service create --mount type=bind,src=HOST-PATH,dst=CONTAINER-PATH --name myservice IMAGE
# 挂载为只读
docker service create --mount type=bind,src=HOST-PATH,dst=CONTAINER-PATH,readonly --name myservice IMAGE</pre>
<p>绑定挂载很有用，但是也可能很危险：</p>
<ol>
<li>由于任务可能被分配到任何满足条件的节点上，而绑定挂载要求路径预先存在</li>
<li>调度器可能在任何时候重新分配某个任务 </li>
</ol>
<div class="blog_h3"><span class="graybg">管理节点</span></div>
<p>管理节点负责以下集群管理工作：</p>
<ol>
<li>维护集群状态</li>
<li>调度服务</li>
<li>提供Swarm模式的HTTP API端点服务</li>
</ol>
<p>基于Raft（一种算法），管理节点维护整个Swarm、所有运行中服务的一致性内部状态。</p>
<p>在测试环境下，你可以使用单管理节点，但是，一点此节点宕机，你需要重新创建Swarm才能恢复。</p>
<p>为了利用Swarm的容错特性，最好建立奇数节点数的管理节点。如果有多个管理节点，Swarm可以自动从管理节点宕机中恢复，没有downtime。当总计3个管理节点时，可以容忍1个宕机；当5个管理节点时，可以容忍2个宕机；当N个管理节点时，可以容忍(N-1)/2个管理节点宕机。Docker推荐每个Swarm中有7个管理节点。</p>
<div class="blog_h3"><span class="graybg">工作节点 </span></div>
<p>工作节点的唯一任务就是执行容器。默认的，管理节点同时也是工作节点。</p>
<p>要阻止派发任务给管理节点，可以将后者的可用性设置为Drain，调度器会优雅的停止Drain上运行的任务并且在其它节点上重新调度。</p>
<div class="blog_h2"><span class="graybg">服务如何工作</span></div>
<p>要在Swarm模式下部署应用程序，你需要创建一个“服务”。通常情况下，服务是某个较大应用程序的上下文中的某个“微服务“， 例如HTTP服务、数据库服务、或者任何形式的可执行程序。</p>
<p>当创建服务时，你需要指定使用什么镜像，以及在基于此镜像的容器中运行什么命令。你可以同时指定：</p>
<ol>
<li>Swarm暴露的端口，这使得服务对外可用</li>
<li>让服务可以与Swarm中其它服务进行通信的Overlay网络</li>
<li>CPU和内存限额、预留</li>
<li>滚动更新策略</li>
<li>服务的复制（replicas）份数</li>
</ol>
<div class="blog_h3"><span class="graybg">服务/任务/容器</span></div>
<p>当你在Swarm中部署服务时，Swarm接受你给出的服务定义（service definition），作为目标服务的期望状态（desired state ）。之后，Swarm调度服务，形成在节点上运行的一个或N个任务。每个任务独立于集群中其它节点运行。</p>
<p>下面是具有三个Replica的HTTP服务的例子：</p>
<p><img class="size-full wp-image-14678 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2015/10/services-diagram.png" alt="services-diagram" width="95%" /></p>
<p>容器是一个被隔离的进程，在Swarm模式的模型中，每个任务调用仅一个容器。任务就好像是一个插槽，调度器将容器插入其中。一旦容器开始运行，调度器将任务设置为RUNNING状态；如果容器未通过健康检查、停止运行，则任务也被终结。</p>
<div class="blog_h3"><span class="graybg">任务和调度</span></div>
<p>任务是Swarm调度的原子单元。当你通过创建/更新服务来声明服务的期望状态时，编排器（orchestrator）识别出被调度任务的期望状态——当你声明保持3个HTTP服务一直运行，编排器就会创建三个任务。</p>
<p>任务是一个插槽，调度器产生容器进程并填充到插槽。容器是任务的实例。任务是一种单向的机制，它从：已分配（assigned）、已准备（prepared）、正在运行（running）等一系列状态单向的前进。如果某个任务未通过健康检查或者终结，任务及其容器被编排器移除，新的副本任务以及对应的容器被创建，以满足期望状态。</p>
<p>Swarm模式底层组件包括了一般性用途的调度器、编排器。服务、任务的抽象实现，不理解容器这个概念。理论上你可以实现在非容器中运行的服务。</p>
<p>下图说明Swarm模式如何接受用户创建服务的请求，如何调度服务：</p>
<p><img class="alignnone size-full wp-image-14680" src="https://blog.gmem.cc/wp-content/uploads/2015/10/service-lifecycle.png" alt="service-lifecycle" width="95%" /></p>
<div class="blog_h3"><span class="graybg">悬挂服务</span></div>
<p>可以配置服务未悬挂的（pending），这样Swarm中没有节点可以运行该服务的任务。如果你仅仅需要防止服务被部署，只需要Scale到0，而不是尝试让服务进入pending状态。</p>
<p>下列情况下，服务会变为pending：</p>
<ol>
<li>如果所有节点被paused或者drained，那么新创建的服务会保持pending状态，直到某个节点可用。在实际情况下，第一个可用的节点会获得所有任务</li>
<li>你可以为任务保留一定量的内存，如果Swarm中所有节点都没有足够的内存，则服务保持pending状态</li>
</ol>
<div class="blog_h3"><span class="graybg">复制/全局服务</span></div>
<p>从部署份数的角度来看，服务可以分为复制（replicated）、全局（global）两种。</p>
<p>复制服务，由指定数量的任务构成。全局服务则在每个节点运行单个任务。</p>
<div class="blog_h2"><span class="graybg">管理敏感数据</span></div>
<p>Swarm模式下，我们可以使用Docker secrets管理敏感数据。所谓Secret是指一块数据，存放密码、SSH私钥、SSL证书或者其它不应该通过网络传递、明文存放在Dockerfile中的信息。</p>
<div class="blog_h1"><span class="graybg">Docker Compose</span></div>
<p>Compose是用于定义、运行多容器Docker应用程序的工具。通过编辑一个Compose文件，你可以配置应用程序的服务组件，然后，只需要单条命令，你就可以创建、启动所有需要的服务。</p>
<p>使用Compose通常包括以下三大步骤：</p>
<ol>
<li>使用Dockerfile定义应用程序的环境，以便可以在任何地方重现</li>
<li>在文件docker-compose.yml中定义构成应用的服务组件，以便它们可以在一个隔离的环境下运行</li>
<li>最后，执行<pre class="crayon-plain-tag">docker-compose up</pre> 启动整个应用</li>
</ol>
<div class="blog_h2"><span class="graybg">特性列表</span></div>
<table class=" fixed-word-wrap full-width">
<tbody>
<tr>
<td>
<p><strong>单个宿主机上的多个隔离环境</strong></p>
<p>Compose使用工程名称（project name）来隔离环境，你可以在几个上下文中使用工程名称：</p>
<ol>
<li>在开发机上，创建单个环境的多个副本</li>
<li>在持续集成（CI）服务器上，为了防止构建之间的相互干扰，你可以把构建号设置为工程名称</li>
<li>在共享主机/开发主机上，用于防止可能使用相同服务名的不同项目的相互干扰</li>
</ol>
<p>工程名称默认为工程的目录名，你可以使用-p选项或者 COMPOSE_PROJECT_NAME 环境变量设置工程名称</p>
</td>
</tr>
<tr>
<td>
<p><strong>在创建容器时保留卷数据</strong></p>
<p>Compose会保留你的服务使用的所有卷。当执行docker-compose up时，如果发现某个容器之前运行过，则将其卷复制给新容器实例，确保你在卷中的数据不丢失</p>
</td>
</tr>
<tr>
<td>
<p><strong>仅重建变化了的容器</strong></p>
<p>Compose会缓存用于创建容器的配置信息，当你重启一个没有变化的服务时，它的容器会被重用，而不是重新创建</p>
</td>
</tr>
<tr>
<td>
<p><strong>支持变量</strong></p>
<p>你可以在Compose文件中设置变量，以便为不同的运行环境定义服务组合</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">应用场景</span></div>
<p>Compose的典型应用场景包括：</p>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td>
<p><strong>开发环境</strong></p>
<p>在开发软件时，能够在隔离环境中运行应用程序，并与之交互很关键。Compose很适合创建这样的环境</p>
<p>Compose文件能够配置应用程序的所有依赖（数据库、消息队列、缓存、WebService，等等）。通过一条命令，你可以为每个依赖启动一个或者多个容器</p>
</td>
</tr>
<tr>
<td>
<p><strong>自动化测试环境</strong></p>
<p>自动化测试套件是CI的重要组成部分。自动化的段对端测试要求一个运行环境，Compose可以方便的创建、销毁隔离的测试环境</p>
</td>
</tr>
<tr>
<td>
<p><strong>部署环境</strong></p>
<p>Compose典型的应用场景在开发、测试工作流中。但是，你也可以用Compose部署应用到远程Docker——包括单个Docker引擎或者整个Swarm集群</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">安装</span></div>
<p>Compose可以在macOS、Windows或者64位Linux上运行。在Linux上的安装步骤可以参考：</p>
<pre class="crayon-plain-tag">sudo curl -L "https://github.com/docker/compose/releases/download/1.11.2/docker-compose-$(uname -s)-$(uname -m)" \
    -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# 查看版本
docker-compose --version</pre>
<div class="blog_h1"><span class="graybg">Docker Machine</span></div>
<p>使用Docker Machine，你可以：</p>
<ol>
<li>在Windows或者Mac上安装、运行Docker。在1.12之前，DM是唯一在Windows/Mac上运行Docker的途径，之后这两个平台有了Native的Docker实现</li>
<li>划分/管理多台远程Docker宿主机。可以自动在宿主机上划分虚拟机，并在虚拟机上安装Docker</li>
<li>划分/管理Docker Swarm集群</li>
</ol>
<p>Docker Machine是一套工具，它允许你在虚拟主机上安装Docker引擎，以及通过docker-machine命令来管理宿主机（Machine，通常是虚拟机，这些虚拟机通常由DM创建）。你可以使用DM在非Linux系统、公司网络、数据中心、云端来创建Docker宿主机。</p>
<p>使用docker-machine命令，你可以启动、查看、停止受管理的宿主机，升级Docker客户端/守护程序。</p>
<div class="blog_h2"><span class="graybg">安装</span></div>
<div class="blog_h3"><span class="graybg">Linux</span></div>
<p>执行以下命令下载并安装：</p>
<pre class="crayon-plain-tag">curl -L https://github.com/docker/machine/releases/download/v0.10.0/docker-machine-`uname -s`-`uname -m` &gt;/tmp/docker-machine
chmod +x /tmp/docker-machine
sudo cp /tmp/docker-machine /usr/local/bin/docker-machine

# 执行命令查看版本信息
docker-machine version</pre>
<div class="blog_h2"><span class="graybg">起步</span></div>
<div class="blog_h3"><span class="graybg">使用DM运行容器</span></div>
<p>步骤通常为：</p>
<ol>
<li>创建一个新的（或者启动既有的）宿主机</li>
<li> 切换环境到宿主机</li>
<li>使用Docker客户端创建、载入、管理容器</li>
</ol>
<div class="blog_h3"><span class="graybg">创建宿主机</span></div>
<p>使用下面的命令创建新的宿主机：</p>
<pre class="crayon-plain-tag"># 在不支持Hyper-V的老Windows下或者在Mac下，使用virtualbox驱动
# 在支持Hyper-V的Windows下，使用hyperv驱动
docker-machine create --driver virtualbox default</pre>
<p>注意，在Linux下需要VirtualBox预先被安装。</p>
<p>使用命令<pre class="crayon-plain-tag">docker-machine ls</pre> 可以列出现有的虚拟机。</p>
<p>使用下面的命令，可以切换环境变量，指向新创建的宿主机：</p>
<pre class="crayon-plain-tag"># 这个命令用于获取环境变量，这些环境变量导致当前Shell的连接目标改变
docker-machine env default
# 输出
# export DOCKER_TLS_VERIFY="1"
# export DOCKER_HOST="tcp://172.16.62.130:2376"
# export DOCKER_CERT_PATH="/home/alex/.docker/machine/machines/default"
# export DOCKER_MACHINE_NAME="default"

# 执行下面的命令，切换到default这台宿主机
eval "$(docker-machine env default)"</pre>
<p>现在，你可以针对宿主机进行操作了。要停止或者启动宿主机，可以：</p>
<pre class="crayon-plain-tag">docker-machine stop default
docker-machine start default</pre>
<div class="blog_h3"><span class="graybg">连接到宿主机</span></div>
<p>你也可以连接到既有的宿主机，好处是，管理这台宿主机是，不需要每次提供URL：</p>
<pre class="crayon-plain-tag">docker-machine create --driver none --url=tcp://10.0.0.1:2376 fedora-10</pre>
<p>宿主机必须预先配置，支持基于TLS的连接。 </p>
<div class="blog_h1"><span class="graybg">安全性</span></div>
<div class="blog_h2"><span class="graybg">Docker安全性</span></div>
<p>检查Docker安全性时，有4个主要方面需要考虑：</p>
<ol>
<li>内核本身的安全性：名字空间的支持、Cgroups</li>
<li>Docker守护程序本身的攻击面（attack surface ）</li>
<li>容器配置的漏洞，这些漏洞可能是默认就附带的，或者用户定制而引入的</li>
<li>内核中固化的安全特性，这些特性如何与容器交互</li>
</ol>
<div class="blog_h3"><span class="graybg">内核名字空间</span></div>
<p>Docker容器与LXC容器很类似，它们具有相似的安全特性。当你启动容器时，Docker会为容器创建一系列的名字空间和控制组。</p>
<p>名字空间提供第一级的、最直接的隔离性——容器中运行的进程看不到，甚至不能影响到其它容器、宿主机中运行的进程。内核名字空间机制从2.6.15 - 2.6.26版本开始引入，目前已经非常稳定。</p>
<p>每个容器具有自身的网络栈（network stack），这意味着容器不具有其它容器套接字、网络接口的访问权限。当然，如果宿主机正确的配置，容器之间可以基于各自的网络接口进行交互，就像与外部主机一样。</p>
<div class="blog_h3"><span class="graybg">控制组</span></div>
<p>Cgroups是Linux容器的另一个关键组件，它实现了资源审计和限额，并提供很多有价值的度量信息。利用控制组，可以让容器获得公平的CPU、内存、磁盘I/O等资源。Cgroups能够有效的防范某些DoS攻击。Cgroups于2.6.24被合并到内核。</p>
<div class="blog_h3"><span class="graybg">守护进程攻击面</span></div>
<p>通过Docker运行容器，意味着需要运行Docker守护程序，后者需要Root权限。只有受信任用户才应该被允许控制守护程序。</p>
<p>由于Docker允许在宿主机、容器之间共享目录，这意味着容器可能任意的修改宿主机文件系统。</p>
<p>当在服务器上运行Docker时，推荐宿主机仅仅运行Docker，而把所有其它服务（除了SSH服务器这类管理工具）都放在容器中运行。</p>
<div class="blog_h3"><span class="graybg">内核能力</span></div>
<p>默认的，Docker以一组受限的能力（capabilities）来启动容器。能力把root/非root划分为细粒度的访问控制系统。需要绑定到1024-端口的进程不再需要root权限，而仅需要被授予net_bind_service能力。</p>
<p>容器也不需要被授予真正的root权限。事实上，容器中的root用户缺陷受到很大的限制，例如：</p>
<ol>
<li>禁止任何mount操作</li>
<li>禁止访问原始套接字（避免包嗅探）</li>
<li>禁止某些文件系统操作，例如创建新设备节点、修改文件所有权、修改属性</li>
<li>禁止模块加载操作</li>
</ol>
<p>由于这些限制的存在，即使攻击者获得容器的root权限，也难以进行严重的破坏。</p>
<p>你可以增加、删除容器的能力，以提升功能或安全性。</p>
<div class="blog_h3"><span class="graybg">其它内核特性</span></div>
<p>能力（capabilities）仅仅是现代Linux内核提供的众多安全特性之一。其它已知的著名安全系统包括：TOMOYO、AppArmor、SELinux、GRSEC等，它们都可以和Docker协作。</p>
<p>考虑以下建议：</p>
<ol>
<li>可以基于GRSEC、PAX来运行内核。这样会增加额外的安全检查（编译时、运行时）并防御很多缺陷。不需要针对Docker的配置，因为这些安全特性是系统级的</li>
<li>如果你使用的发行版提供了针对Docker容器的安全模型模板（security model templates），你可以使用它们</li>
<li>你可以使用某种访问控制系统，定义自己的安全策略</li>
</ol>
<p>从1.10开始，用户名字空间被Docker直接支持。这一特性允许容器中的root用户直接映射到容器外部的非0 UID的任何用户，进而减少安全风险。这一特性默认没有开启。</p>
<div class="blog_h2"><span class="graybg">保护守护进程套接字</span></div>
<p>默认的，Docker通过非网络化的Unix套接字运行，你也可以基于HTTP套接字与之通信。</p>
<p>如果你期望通过网络安全的访问Docker，应当启用TLS。这样，在守护程序端，仅仅通过CA认证的客户端才允许连接；在客户端，则仅仅允许向通过CA认知的服务器发起连接。</p>
<p>在守护程序上启用TLS的示例：</p>
<pre class="crayon-plain-tag">dockerd --tlsverify --tlscacert=ca.pem --tlscert=server-cert.pem --tlskey=server-key.pem -H=0.0.0.0:2376</pre>
<p>在客户端上启用TLS的示例：</p>
<pre class="crayon-plain-tag">docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem  -H=127.0.0.1:2376 version</pre>
<div class="blog_h2"><span class="graybg">使用受信任镜像</span></div>
<p>在使用Docker的过程中，我们常常需要从/到Docker Hub或者私服pull/push镜像。内容信任（Content trust）机制允许验证数据的完整性、镜像的发布者，不论你是从什么渠道获得镜像。</p>
<div class="blog_h3"><span class="graybg">理解Docker中的信任</span></div>
<p>内容信任机制可以强制客户端在与远程服务（Hub或私服，registry）交互时，进行客户端签名和镜像Tag验证。该机制默认情况下是禁用的，要启用，可以设置环境变量<pre class="crayon-plain-tag">DOCKER_CONTENT_TRUST</pre> 为1。</p>
<p>一旦内容信任被启用，镜像发布者就可以对自己的镜像进行签名。镜像的消费者则可以确保镜像是来自发布者，未经篡改。</p>
<p>每一个镜像记录由以下字段唯一的标识：<pre class="crayon-plain-tag">[REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]</pre> 。一个镜像仓库（REPOSITORY）可以具有多个标签，镜像构建者可以使用仓库+标签的组合多次构建并更新镜像。</p>
<p>内容信任与TAG部分关联，每个REPOSITORY具有一组供发布者签名镜像TAG的密钥。单个REPOSITORY中可以包含签名、未签名的TAG。对于启用内容信任的消费者，未签名的TAG是不可见的。</p>
<p>子命令push、build、create、pull、run与内容信任机制交互。例如，当你执行docker pull someimage:latest时，仅当someimage:latest被正确签名时，命令才会成功。除了指定TAG，你也可以直接指定签名Hash：</p>
<pre class="crayon-plain-tag">docker pull someimage@sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a</pre>
<p>与镜像标签信任管理相关的是一系列的签名密钥。当第一次使用到内容信任功能时，密钥被创建。这些密钥包括：</p>
<ol>
<li>作为内容信任根的离线密钥，该密钥属于发布镜像的组织或个人，必须被客户端妥善的秘密保存，丢弃此密钥将非常难以恢复</li>
<li>签名仓库、标签的密钥，该密钥与一个镜像仓库关联，使用该密钥可以pull/push模板仓库的任何标签，保存在客户端</li>
<li>服务器管理的密钥，例如时间戳密钥。保存在服务器端</li>
</ol>
<div class="blog_h3"><span class="graybg">典型内容信任操作</span></div>
<p>除了通过环境变量来全局性的启用内容信任之外，你还可以在调用docker命令时指定<pre class="crayon-plain-tag">--disable-content-trust</pre> 来临时的禁用内容信任：</p>
<pre class="crayon-plain-tag">docker build --disable-content-trust -t gmemcc/nottrusttest:latest
docker pull --disable-content-trust gmemcc/nottrusttest:latest
docker push --disable-content-trust gmemcc/nottrusttest:latest</pre>
<div class="blog_h3"><span class="graybg">推送签名内容</span></div>
<p>当你第一次推送受信任内容时，Docker会提示你：</p>
<ol>
<li>警告你，新的root密钥将被创建 </li>
<li>提示输入root密钥的密码</li>
<li>在~/.docker/trust目录生成root密钥</li>
<li>提示输入仓库密钥的密码</li>
<li>在~/.docker/trust目录生成仓库密钥</li>
</ol>
<div class="blog_h3"><span class="graybg">拉取签名内容</span></div>
<p>如果启用内容信任机制，没有被签名的镜像是无法拉取的。</p>
<div class="blog_h3"><span class="graybg">自动化构建</span></div>
<p>要使用自动化脚本来执行镜像TAG签名，你需要设置环境变量：</p>
<ol>
<li>DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE  根密钥的密码</li>
<li>DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE  仓库密钥的密码</li>
</ol>
<div class="blog_h1"><span class="graybg">常用命令</span></div>
<p>参考<a href="https://docs.docker.com/engine/reference/commandline/docker/">Engine(docker) CLI</a>。</p>
<div class="blog_h2"><span class="graybg">docker</span></div>
<p>本质上此命令行通过REST  API和守护程序通信，和守护程序一样。该命令支持HTTP_PROXY、HTTPS_PROXY，并且优先使用后者。</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">build</td>
<td>
<p>基于Dockerfile构建镜像</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker build [OPTIONS] PATH | URL | -</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>--build-arg value  设置构建时（容器运行时看不到）变量，例如环境变量<br /> --cgroup-parent string 容器的父Cgroup组<br /> --cpu-period int  限制CFS周期<br /> --cpu-quota int 限制CFS配额<br /> -c, --cpu-shares int  限制CPU权重<br /> --cpuset-cpus string 限制允许在哪些CPU上执行<br /> --cpuset-mems string  限制允许使用哪些内存条<br /> --disable-content-trust  禁止镜像验证，默认true<br /> -f, --file string  可选的Dockerfile名称<br /> --force-rm  总是移除中间容器<br /> --isolation string  容器隔离计数<br /> --label value  在镜像上设置元标签<br /> -m, --memory string  设置内存限制<br /> --memory-swap string   内存和交换文件限制<br /> --no-cache   在构建镜像时不使用缓存<br /> --pull   总是尝试拉取更新版本的镜像<br /> -q, --quiet  禁止输出内容，仅在成功时打印镜像ID<br /> --rm   在成功构建后，移除中间容器，默认true<br /> --shm-size string  /dev/shm的大小，默认64MB<br /> -t, --tag value  为镜像设置标签<br /> --ulimit value  Ulimit选项</p>
</td>
</tr>
<tr>
<td class="blog_h3">info</td>
<td>查看Docker守护程序的基本信息</td>
</tr>
<tr>
<td class="blog_h3">images</td>
<td>
<p>列出本地可用的镜像</p>
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker images [OPTIONS] [REPOSITORY[:TAG]]</pre>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-a, --all  显示所有镜像，默认情况下中间镜像被隐藏<br /> --digests 显示摘要<br /> -f, --filter value  根据条件过滤输出<br /> --format string  依据Go语言模板指定输出的格式化方式<br /> --no-trunc  不截断输出<br /> -q, --quiet  安静模式，仅显示容器ID，不显示表头</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag">docker images             # 列出所有镜像
docker images java        # 列出所有Repository为java的镜像
docker images java:8      # 同时限定Repository和tag</pre>
</td>
</tr>
<tr>
<td class="blog_h3">rmi</td>
<td>
<p><span style="background-color: #c0c0c0;">示例：</span>
<pre class="crayon-plain-tag"># 删除所有镜像
docker rmi `docker images -q` 

# 删除无标签镜像
docker rmi $(docker images | grep "^&lt;none&gt;" | awk "{print $3}")</pre>
</td>
</tr>
<tr>
<td class="blog_h3">commit</td>
<td>
<p>把容器的变更提交到一个新的镜像中
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-a, --author string  镜像作者信息，例如Alex &lt;alex@gmem.cc&gt;<br /> -c, --change value 为新镜像应用Dockerfile指令<br /> -m, --message string  提交注释<br /> -p, --pause 在提交期间暂停容器</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 提交为镜像，并修改环境变量、入口点、暴露端口
docker commit --change "ENV DEBUG true"  CONTAINER_ID REGISTRY/REPO:TAG
              -c 'CMD ["apachectl", "-DFOREGROUND"]'
              -c 'EXPOSE 80' </pre>
</td>
</tr>
<tr>
<td class="blog_h3">run</td>
<td>
<p>从一个镜像创建容器并运行指定的命令
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker run [OPTIONS] IMAGE [COMMAND] [ARG...] </pre>
<p><span style="background-color: #c0c0c0;">选项：</span><br /> --add-host value  添加自定义的主机:IP地址映射<br /> -a, --attach  关联STDIN、STDOUT或者STDERR<br /> --blkio-weight value  块I/O相对权重，10-1000之间<br /> --blkio-weight-device value  块I/O设备相对权重<br /> --cap-add value  添加Linux特性（capabilities）<br /> --cap-drop value  去除Linux特性（capabilities）<br /> --cgroup-parent string  为容器指定父cgroup<br /> --cidfile string  输出容器ID到目标文件<br /> --cpu-percent int  CPU占用百分比，仅Windows<br /> --cpu-period int  限制容器使用的CFS周期<br /> --cpu-quota int  限制容器使用的CFS配额<br /> -c, --cpu-shares int  限制容器使用的CPU相对权重<br /> --cpuset-cpus string  限制容器可以使用的CPU的序号，例如0-3, 0,1<br /> --cpuset-mems string  限制容器可以使用的内存的变号，例如0-3, 0,1<br /> -d, --detach    在后端运行容器，并打印容器的ID<br /> --detach-keys    设置解除终端与容器关联的快捷键<br /> --device value    添加一个宿主机设备给容器<br /> --device-read-bps value  以字节/秒为单位限制容器读取一个设备的速度<br /> --device-read-iops value 以次数/秒为单位限制容器读取一个设备的速度<br /> --device-write-bps value 以字节/秒为单位限制容器写入一个设备的速度<br /> --device-write-iops value 以次数/秒为单位限制容器写入一个设备的速度<br /> --disable-content-trust  跳过镜像安全性验证<br /> --dns value  指定容器使用的DNS服务器<br /> --dns-opt value  设置容器的DNS选项<br /> --dns-search  设置自定义的DNS查找域<br /> --entrypoint  覆盖镜像默认的入口点（ENTRYPOINT）配置<br /> -e, --env value  设置环境变量，示例：<pre class="crayon-plain-tag">-e "LIMIT=10"</pre><br /> --env-file value  从文件中读取环境变量<br /> --expose value  暴露一个端口，作用类似于Dockfile中的expose命令。从1.5开始，支持暴露一组端口，例如<pre class="crayon-plain-tag">--expose=7000-8000</pre><br /> --group-add value   指定额外需要加入的组<br /> --health-cmd string   指定容器健康状态检查的命令<br /> --health-interval duration  容器健康状态检查间隔<br /> --health-retries int  报告为不健康之前重新检查的次数<br /> --health-timeout duration  一次健康检查最多执行的时间<br /> -h, --hostname string  容器的主机名<br /> -i, --interactive    即使没有关联到终端，也保持容器的标准输入打开<br /> --io-maxbandwidth string   系统驱动器最大IO带宽限制，仅Windows<br /> --io-maxiops uint  系统驱动器最大IOPS限制，仅Windows <br /> --ip string  设置容器的IPv4地址<br /> --ip6 string  设置容器的IPv6地址<br /> --ipc string  容器使用的IPC名字空间，设置为host则与宿主机共享IPC名字空间<br /> --isolation string  容器隔离技术<br /> --kernel-memory string  内核内存限制<br /> -l, --label value  为容器指定标签（元数据）<br /> --label-file value  从逗号分隔符文件中读取标签<br /> --link value  链接到其它容器，可以允许docker0网络中两个容器通信<br /> --link-local-ip value 设置容器的IPv4/IPv6 link-local地址<br /> --log-driver string  容器的日志驱动（Logging driver）。可选None不显示日志；Json-file默认值，以JSO格式记录日志；Syslog把日志输出到系统日志文件；journald、fluentd、splunk、gelf、awslogs<br /> --log-opt value  容器日志驱动的选项<br /> --mac-address string  设置容器的MAC地址<br /> -m, --memory string  设置容器的内存用量限额<br /> --memory-reservation string 设置容器的内存用量软限制<br /> --memory-swap string  设置交换文件限额，-1表示不限制<br /> --memory-swappiness int  微调容器的swappiness，0-100之间<br /> --name string   为容器分配名称<br /> --network string  连接容器到指定的网络<br /> --network-alias value  为容器指定目标网络范围内的别名<br /> --no-healthcheck  禁止容器特定的健康状态检查<br /> --oom-kill-disable   禁止内存溢出Killer<br /> --oom-score-adj int  微调宿主机的OOM参数，-1000到1000之间<br /> --pid string PID  使用的名字空间类型，取值host则与宿主机共享PID名字空间<br /> --pids-limit int  微调容器的PIDs限制<br /> --privileged  为容器授予扩展的权限<br /> -p, --publish value  发布容器暴露的端口到宿主机，value格式 <span style="background-color: #c0c0c0;">宿主机端口:容器端口</span><br /> -P, --publish-all Publish  随机的发布容器暴露的端口到宿主机，通过--expose指定或者在Dockerfile中以EXPOSE指定的任意端口都会被发布。宿主机的端口范围由/proc/sys/net/ipv4/ip_local_port_range这个内核参数确定，默认32768-61000之间<br /> --read-only  以指定方式挂载容器的根文件系统<br /> --restart string  设置容器退出时的重启/宿主机开机后的启动策略，默认no。On-failure当容器命令返回非0时重启，Always 自动重启，并且总是随着守护程序启动，Unless-stopped类似于Always，但是不随着守护程序启动<br /> --rm  在容器退出时自动删除它<br /> --runtime string  设置容器使用的运行时间<br /> --security-opt value  设置安全选项<br /> --shm-size string Size  设置/dev/shm的大小，默认64MB<br /> --sig-proxy  代理接收到的信号给容器进程<br /> --stop-signal string  用于退出容器的信号，默认SIGTERM<br /> --storage-opt value  容器的存储驱动选项，devicemapper、overlay2等支持<br /> --sysctl value  容器的Sysctl选项<br /> --tmpfs value  挂载一个tmpfs目录<br /> -t, --tty  为容器分配一个伪终端<br /> --ulimit value  设置Ulimit选项<br /> -u, --user string  设置容器执行身份，格式&lt;name|uid&gt;[:&lt;group|gid&gt;]<br /> --userns string  设置使用的用户名字空间<br /> --uts string  设置使用的UTS名字空间，设置为host则与宿主机使用相同的hostname和domain<br /> -v, --volume value  挂载一个卷<br /> --volume-driver string  可选的卷驱动<br /> --volumes-from value  从指定的容器挂载卷<br /> -w, --workdir string  设置容器的工作目录</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 交互式的运行一个容器，分配伪终端，在退出时删除容器
docker run  -i -t --rm --name=temp ubuntu:14.04
# 指定存储驱动选项
docker run --storage-opt size=120G ubuntu
# 挂载一个虚拟内存文件系统，并指定选项
docker run --tmpfs /run:rw,noexec,nosuid,size=65536K
# 指定环境变量（命令行、文件）
docker run -e NAME=VAL --env-file=path-to-file
# 挂载其它容器定义的卷，指定ro/rw
docker run --volumes-from CONTAINER_ID:rw
# 修改容器hosts文件
docker run --add-host=zircon:10.0.0.1
# 限制容器的资源用量 type=softlimit[:hardlimit]
docker run --ulimit nofile=1024:1024</pre>
</td>
</tr>
<tr>
<td class="blog_h3">create</td>
<td>
<p>创建，但不启动容器
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker create [OPTIONS] IMAGE [COMMAND] [ARG...]</pre> </p>
</td>
</tr>
<tr>
<td class="blog_h3">start</td>
<td>
<p>启动一个现有的容器</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker start [OPTIONS] CONTAINER [CONTAINER...]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span><br />-a, --attach  关联容器的标准输入/输出到当前终端，并转发信号<br />-i, --interactive  关联容器的标准输入到当前终端<br />--detach-keys string  覆盖解除终端与容器关联的快捷键序列</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 交互式启动容器，并关联当前终端到该容器
docker start -i -a ubuntu</pre>
</td>
</tr>
<tr>
<td class="blog_h3">stop</td>
<td>
<p>停止一个运行中的容器
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker stop [OPTIONS] CONTAINER [CONTAINER...]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-t, --time int  强制杀死前，等待的秒数，默认10</p>
</td>
</tr>
<tr>
<td class="blog_h3">kill</td>
<td>
<p>杀死一个或者多个容器</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker kill [OPTIONS] CONTAINER [CONTAINER...]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-s, --signal string  用于杀死容器的信号，默认KILL</p>
</td>
</tr>
<tr>
<td class="blog_h3">attach</td>
<td>
<p>关联当前终端到运行中的容器，之后你可以按Ctrl+P , Ctrl+Q解除关联（保持容器运行）</p>
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker attach [OPTIONS] CONTAINER</pre>
<p><span style="background-color: #c0c0c0;">选项：</span><br />--detach-keys string 覆盖解除终端与容器关联的快捷键序列<br />--sig-proxy 代理接收到的信号给容器进程<br />--no-stdin   不关联标准输入</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 执行此命令后，容器进入前台运行
docker attach ubuntu</pre>
</td>
</tr>
<tr>
<td class="blog_h3">exec</td>
<td>
<p>在运行中的容器里执行命令
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>--detach,-d 后台模式运行<br />--env,-e 设置环境变量<br />-i 交互模式，保持标准输入打开<br />-t 分配伪终端</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag">docker exec -d touch /etc/config</pre>
</td>
</tr>
<tr>
<td class="blog_h3">rm</td>
<td>
<p>删除容器
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker rm [OPTIONS] CONTAINER [CONTAINER...]</pre>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-f, --force  强制删除，如果目标容器正在运行则发送SIGKILL信号<br /> -l, --link  移除指定的链接<br /> -v, --volumes  移除容器关联的卷</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 删除所有容器
docker rm `docker ps --no-trunc -aq` 
# 删除所有停止的容器
docker ps --filter "status=exited" --quiet |  xargs --no-run-if-empty docker rm</pre>
</td>
</tr>
<tr>
<td class="blog_h3">ps</td>
<td>
<p>列出现有的容器
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker ps [OPTIONS]</pre>
<p><span style="background-color: #c0c0c0;">选项：</span><br />-a, --all  显示所有容器，包括已经停止的<br />-f, --filter value  过滤输出<br />--format string  依据Go语言模板指定输出的格式化方式<br />-n, --last int   显示最后int个创建的容器<br />-l, --latest   显示最后一个创建的容器<br />-q, --quiet   安静模式，仅显示容器ID，不显示表头<br />-s, --size  打印总的空间占用</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 显示已经退出的所有容器的ID
docker ps --filter "status=exited" --quiet</pre>
</td>
</tr>
<tr>
<td class="blog_h3">logs</td>
<td>
<p>抓取一个容器的日志
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker logs [OPTIONS] CONTAINER</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>--details  显示额外信息<br /> -f, --follow  跟随日志输出<br /> --since string 显示指定时间戳之后的日志<br /> --tail string 显示末尾N行日志<br /> -t, --timestamps 显示时间戳</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 跟踪日志输出
docker logs -f ubuntu
# 要删除容器的日志，参考如下脚本： Linux only
rm $(docker inspect $1 | grep -G '"LogPath": "*"' | sed -e 's/.*"LogPath": "//g' | sed -e 's/",//g');</pre>
</td>
</tr>
<tr>
<td class="blog_h3">cp</td>
<td>
<p>从容器中复制文件到宿主机，或者从宿主机拷贝文件到容器
<p>格式：</p>
<pre class="crayon-plain-tag">docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH</pre>
<p>示例：</p>
<pre class="crayon-plain-tag">docker cp apache2:/usr/lib/php5/20131226 /home/alex/Docker/projects/apache2/usr/lib/php5/ </pre>
</td>
</tr>
<tr>
<td class="blog_h3"> stat</td>
<td>
<p>显示容器的资源使用情况
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker stats [OPTIONS] [CONTAINER...]</pre>
<p><span style="background-color: #c0c0c0;">选项：</span><br />-a, --all显示所有容器<br />--no-stream 仅打印一次统计信息，然后立即退出而不是刷新</p>
</td>
</tr>
<tr>
<td class="blog_h3">network</td>
<td>
<p>网络相关子命令</p>
<p><span style="background-color: #c0c0c0;">通用网络选项：</span><br />--internal 禁止通过网络进行外部访问<br />--ipv6  启用IPv6支持</p>
<p><span style="background-color: #c0c0c0;">桥接网络选项：</span></p>
<p>com.docker.network.bridge.name  Linux网桥的名称<br />com.docker.network.bridge.enable_ip_masquerade，--ip-masq  启用IP遮掩<br />com.docker.network.bridge.enable_icc，--icc  启用或禁止跨容器连接性（Inter Container Connectivity）<br />com.docker.network.bridge.host_binding_ipv4 ，--ip  绑定容器暴露的端口时，使用的宿主机IP<br />com.docker.network.driver.mtu，--mtu  设置最大传输单元</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 列出可用的网络
docker network ls

# 查看网络bridge的详细信息，包括连接到网络的容器信息
docker network inspect bridge

# 将容器连接到已经存在的网络，连接后容器立刻可以和网络中的其它容器通信
docker network connect isolated_nw container2
# 断开容器到网络的连接
# -f表示强制断开，可以解决stale endpoints问题
docker network disconnect -f isolated_nw container2

# 创建一个名为isolated_nw的桥接网络，如果不指定dirver默认使用bridge
docker network create --driver bridge isolated_nw
# 创建网络并提供参数
docker network create --subnet 172.30.0.0/16  \
 --opt com.docker.network.bridge.name=docker_gwbridge \
 --opt com.docker.network.bridge.enable_icc=false \
 docker_gwbridge

# Bridge网络仅支持单个子网，overlay则支持多个
# 强烈建议显式的指定子网

docker network create -d overlay \
 --subnet=192.168.0.0/16 \
 --subnet=192.170.0.0/16 \
 --gateway=192.168.0.100 \
 --gateway=192.170.0.100 \
 --ip-range=192.168.1.0/24 \
 --aux-address="my-router=192.168.1.5" --aux-address="my-switch=192.168.1.6" \
 --aux-address="my-printer=192.170.1.5" --aux-address="my-nas=192.170.1.6" \
 my-multihost-network

# 使用 -o来指定选项
docker network create  \
    -o "com.docker.network.bridge.host_binding_ipv4"="172.23.0.1" my-network

# 移除网络，仅当没有容器连接到网络时，才能移除
docker network rm isolated_nw</pre>
</td>
</tr>
<tr>
<td class="blog_h3">load</td>
<td>
<p>从TAR加载镜像，格式：
<pre class="crayon-plain-tag"># 选项
# -i 指定输入TAR的路径，如果不指定从STDIN读取
docker load [OPTIONS]</pre>
<p>示例：</p>
<pre class="crayon-plain-tag"># 解压并从标准输入加载
gunzip -c busybox.tar.gz  | docker load</pre>
</td>
</tr>
<tr>
<td class="blog_h3">save</td>
<td>
<p>保存一个或者多个镜像到TAR归档文件，镜像的层次历史保留
<p>格式：</p>
<pre class="crayon-plain-tag"># 选项 -o 指定输出文件路径，不指定则输出到STDOUT
docker save [OPTIONS] IMAGE [IMAGE...]</pre>
<p> 示例：</p>
<pre class="crayon-plain-tag"># 保存到标准输出，然后压缩
docker save busybox:1.30.1 | gzip -c &gt; busybox.tar.gz</pre>
</td>
</tr>
<tr>
<td class="blog_h3">import</td>
<td>
<p>从TAR归档文件导入内容，创建一个扁平的文件系统镜像
<p>格式：</p>
<pre class="crayon-plain-tag"># 选项：
# -c 应用Dockerfile指令到新创建的镜像
docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]</pre>
</td>
</tr>
<tr>
<td class="blog_h3">export</td>
<td>
<p>导出一个容器的文件系统为TAR归档文件，镜像层次历史丢失
<p>格式：</p>
<pre class="crayon-plain-tag"># 选项 -o 指定输出文件路径，不指定则输出到STDOUT
docker export [OPTIONS] CONTAINER</pre>
</td>
</tr>
<tr>
<td class="blog_h3">swarm init</td>
<td>
<p>初始化Swarm集群
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker swarm init [OPTIONS]</pre>
<p><span style="background-color: #c0c0c0;">选项：</span><br />--advertise-addr  通知地址，格式ip[:port]<br /> --cert-expiry duration 证书有效期，默认2160h0m0s<br /> --dispatcher-heartbeat duration 分发器心跳时间，默认5s<br /> --external-ca value 指定一个或者多个证书签名端点<br /> --force-new-cluster 强制从当前状态创建新集群<br /> --listen-addr value 监听地址，默认0.0.0.0:2377<br /> --task-history-limit int  任务历史存留限制，默认5</p>
</td>
</tr>
<tr>
<td class="blog_h3">swarm join</td>
<td>
<p>以Worker或/和Manager身份加入到集群</p>
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker swarm join [OPTIONS] HOST:PORT</pre>
<p><span style="background-color: #c0c0c0;">选项：<br /></span>--advertise-addr  通知地址，格式ip[:port]<br />--listen-addr value 监听地址，默认0.0.0.0:2377<br />--token string   用于进入集群的令牌</p>
</td>
</tr>
<tr>
<td class="blog_h3">swarm join-token</td>
<td>
<p>管理加入集群使用的令牌</p>
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker swarm join-token [-q] [--rotate] (worker|manager)</pre>
<p> -q, --quiet 仅仅显示令牌<br /> --rotate 轮换令牌</p>
</td>
</tr>
<tr>
<td class="blog_h3">swarm update</td>
<td>
<p>更新Swarm集群</p>
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker swarm update [OPTIONS]</pre>
<p><span style="background-color: #c0c0c0;">选项：</span><br />--cert-expiry duration 证书有效期，默认2160h0m0s<br />--dispatcher-heartbeat duration 分发器心跳时间，默认5s<br />--external-ca value 指定一个或者多个证书签名端点<br />--task-history-limit int 任务历史存留限制，默认5</p>
</td>
</tr>
<tr>
<td class="blog_h3">swarm leave</td>
<td>
<p>离开一个Swarm集群，仅用于Worker</p>
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker swarm leave [OPTIONS]</pre>
<p><span style="background-color: #c0c0c0;">选项：</span><br />--force  强制离开集群，忽略警告 </p>
</td>
</tr>
<tr>
<td class="blog_h3">node demote</td>
<td>
<p>把一个或者多个节点降级为Worker</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker node demote NODE [NODE...]</pre> </p>
</td>
</tr>
<tr>
<td class="blog_h3">node inspect</td>
<td>
<p>查看一个或者多个节点的详细信息</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker node inspect [OPTIONS] self|NODE [NODE...]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-f, --format string  基于给定的模板进行格式化<br />--pretty  优化输出（不以JSON格式显示）</p>
</td>
</tr>
<tr>
<td class="blog_h3">node ls</td>
<td>
<p>列出集群中的节点</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker node ls [OPTIONS]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-f, --filter value 基于给定的选项过滤输出<br />-q, --quiet  仅仅显示ID</p>
</td>
</tr>
<tr>
<td class="blog_h3">node promote</td>
<td>
<p>把一个或者多个节点提升为Manager</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker node promote NODE [NODE...]</pre> </p>
</td>
</tr>
<tr>
<td class="blog_h3">node rm</td>
<td>
<p>从Swarm中移除一个或者多个节点</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker node rm [OPTIONS] NODE [NODE...]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>--force  强制移除活动节点</p>
</td>
</tr>
<tr>
<td class="blog_h3">node ps</td>
<td>
<p>列出节点上运行的Tasks</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker node ps [OPTIONS] self|NODE</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-f, --filter value 基于给定的选项过滤输出<br /> --no-resolve 不把ID映射为名称</p>
</td>
</tr>
<tr>
<td class="blog_h3">node update</td>
<td>
<p>更新一个节点</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker node update [OPTIONS] NODE</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>--availability string  节点可用性：active/pause/drain<br /> --label-add value 以key=value的形式添加节点标签<br /> --label-rm value  移除一个节点标签（如果存在）<br /> --role string  设置节点的角色：worker/manager</p>
</td>
</tr>
<tr>
<td class="blog_h3">service create</td>
<td>
<p>创建一个新服务</p>
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker service create [OPTIONS] IMAGE [COMMAND] [ARG...]</pre>
<p><span style="background-color: #c0c0c0;">选项：<br /></span></p>
<p>--constraint value 设置约束<br /> --container-label value 设置容器标签<br /> --endpoint-mode string 端点模式dnsrr或者vip<br /> -e, --env value 设置环境变量<br /> -l, --label value 设置服务标签<br /> --limit-cpu value 限制CPU，默认0.000<br /> --limit-memory value 限制内存，默认 0 B<br /> --log-driver string  服务的日志驱动<br /> --log-opt value  日志驱动选项<br /> --mode string  服务模式，replicated（默认）或者 global<br /> --mount value 附加一个挂载到服务<br /> --name string 服务的名称<br /> --network value 加入的网络<br /> -p, --publish value 暴露一个端口，pubPort:targetPort格式<br /> --replicas value   任务的数量，默认0<br /> --reserve-cpu value 保留CPU，默认0.000<br /> --reserve-memory value 保留内存，默认 0 B<br /> --restart-condition string  何时自动重启，none, on-failure, any<br /> --restart-delay value  重启尝试的延迟<br /> --restart-max-attempts value  放弃前重试的重启次数<br /> --restart-window value  用于评估重启策略的窗口<br /> --stop-grace-period value  在杀死容器前，等待的时间<br /> --update-delay duration  更新之间的延迟时间<br /> --update-failure-action string  更新失败后的动作，pause或continue<br /> --update-parallelism uint 同时更新的Task的最大数量，默认1，0表示全部同时更新<br /> -u, --user string   用户名或者UID<br /> --with-registry-auth 发送registry认证详细信息给Swarm代理<br /> -w, --workdir string  设置容器工作目录</p>
</td>
</tr>
<tr>
<td class="blog_h3">service inspect</td>
<td>
<p>查看一个或者多个服务的详细信息</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker service inspect [OPTIONS] SERVICE [SERVICE...]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-f, --format string  基于给定的模板进行格式化<br />--pretty  优化输出（不以JSON格式显示）</p>
</td>
</tr>
<tr>
<td class="blog_h3">service ps</td>
<td>
<p>列出服务包含的任务</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker service ps [OPTIONS] SERVICE</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-f, --filter value 基于给定的选项过滤输出<br />--no-resolve 不把ID映射为名称</p>
</td>
</tr>
<tr>
<td class="blog_h3">service ls</td>
<td>
<p>列出服务</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker service ls [OPTIONS]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-f, --filter value 基于给定的选项过滤输出<br />--no-resolve 不把ID映射为名称</p>
</td>
</tr>
<tr>
<td class="blog_h3">service rm</td>
<td>
<p>移除一个或者多个服务</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker service rm [OPTIONS] SERVICE [SERVICE...]</pre> </p>
</td>
</tr>
<tr>
<td class="blog_h3">service scale</td>
<td>
<p>扩容一个或者多个服务</p>
<p><span style="background-color: #c0c0c0;">格式：</span></p>
<pre class="crayon-plain-tag">docker service scale SERVICE=REPLICAS [SERVICE=REPLICAS...] </pre>
</td>
</tr>
<tr>
<td class="blog_h3">service update</td>
<td>
<p>更新一个服务
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker service update [OPTIONS] SERVICE</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>参考service create子命令</p>
</td>
</tr>
<tr>
<td>system df</td>
<td>查看Docker的磁盘占用情况</td>
</tr>
<tr>
<td>system prune</td>
<td>
<p>删除停止的容器、无人使用的网络、dangling镜像、构建缓存</p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>-a  同时删除没有容器使用的镜像</p>
</td>
</tr>
<tr>
<td class="blog_h3">volume create</td>
<td>
<p>创建一个卷，卷可以被容器消费，并存储数据到其中</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker volume create [OPTIONS] [VOLUME]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>--driver, -d  指定卷驱动名称，默认local<br />--label  为卷添加元数据<br />--name  设置卷的名称，不指定则随机名称。在不同驱动之间，名称不能重复。使用同一种驱动、同一个名称，被认为是卷重用，不会报错<br />--opt, -o  驱动特定的选项</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 创建一个名为hello的卷
docker volume create hello
# 创建一个100MB的tmpfs卷
docker volume create --driver local --opt type=tmpfs \
    --opt device=tmpfs --opt o=size=100m,uid=1000  foo
# 创建一个映射到NFS服务192.168.1.1的/path/to/dir目录的卷
docker volume create --driver local --opt type=nfs \
    --opt o=addr=192.168.1.1,rw--opt device=:/path/to/dir foo

# 挂载卷到容器的/world目录，注意挂载点必须是绝对路径
docker run -d -v hello:/world busybox ls /world</pre>
</td>
</tr>
<tr>
<td class="blog_h3">volume inspect</td>
<td>
<p>显示一个或者多个卷的详细信息
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker volume inspect [OPTIONS] VOLUME [VOLUME...]</pre> </p>
</td>
</tr>
<tr>
<td class="blog_h3">volume ls</td>
<td>
<p>列出可用的卷</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker volume ls [OPTIONS]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>--filter, -f  过滤条件<br />--format 使用Go语言模板优化输出<br />--quiet, -q  仅显示卷名称</p>
</td>
</tr>
<tr>
<td class="blog_h3">volume prune</td>
<td>
<p>移除所有不被使用的卷</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker volume prune [OPTIONS]</pre> </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>--force, -f  强制删除不提示</p>
</td>
</tr>
<tr>
<td class="blog_h3">volume rm</td>
<td>
<p>删除一个或多个卷</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker volume rm [OPTIONS] VOLUME [VOLUME...]</pre>  </p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p>--force, -f 强制删除不提示</p>
</td>
</tr>
<tr>
<td class="blog_h3">manifest</td>
<td>
<p>当前是Docker客户端的试验特性。用于管理清单（manifest）或清单列表</p>
<p>所谓清单，是关于镜像的信息，包括层、大小、摘要值。docker manifest命令可以在清单中增加镜像所针对的操作系统、体系结构的信息</p>
<p>清单列表则是通过指定1或多个镜像名称来创建的层列表。典型情况下，清单列表是基于一组功能相同、针对不同os_arch的镜像</p>
<p>当拉取镜像时，Docker会检查返回的清单信息，如果发现是支持multi arch的清单列表对象，则在列表中<span style="background-color: #c0c0c0;">自动寻找匹配当前体系结构的镜像</span></p>
<p>注意：</p>
<ol>
<li>客户端，需要在~/.docker/config.json中设置 <pre class="crayon-plain-tag">"experimental": "enabled"</pre></li>
</ol>
</td>
</tr>
<tr>
<td class="blog_h3">manifest inspect</td>
<td>
<p>查看镜像清单、或者清单列表的信息</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker manifest inspect [OPTIONS] [MANIFEST_LIST] MANIFEST</pre></p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag">// 查看镜像的清单，获取os_arch信息
// docker manifest inspect hello-world --verbose
{
        "Ref": "docker.io/library/hello-world:latest",
        "Digest": "sha256:f3b3b28a45160805bb16542c9531888519430e9e6d6ffc09d72261b0d26ff74f",
        "SchemaV2Manifest": {
                "schemaVersion": 2,
                "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
                "config": {
                        "mediaType": "application/vnd.docker.container.image.v1+json",
                        "size": 1520,
                        "digest": "sha256:1815c82652c03bfd8644afda26fb184f2ed891d921b20a0703b46768f9755c57"
                },
                "layers": [
                        {
                                "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
                                "size": 972,
                                "digest": "sha256:b04784fba78d739b526e27edc02a5a8cd07b1052e9283f5fc155828f4b614c28"
                        }
                ]
        },
        "Platform": {
                "architecture": "amd64",
                "os": "linux"
        }
} </pre>
</td>
</tr>
<tr>
<td class="blog_h3">manifest create</td>
<td>
<p>创建一个<strong><span style="background-color: #c0c0c0;">本地</span></strong>清单列表，用于后续的标注、推送。清单的存放位置为：
<p style="padding-left: 30px;"><pre class="crayon-plain-tag">${HOME}/.docker/manifests/$(REGISTRY_DOMAIN)_$(REGISTRY_PREFIX)_$(IMAGE)-$(VERSION)</pre></p>
<p>要删除清单，删除上述文件即可。此外你也可以在push的时候指定--purge来删除本地清单</p>
<p>清单必须和镜像一同推送到镜像仓库，否则拉取镜像时会提示找不到manifest</p>
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker manifest create MANIFEST_LIST MANIFEST [MANIFEST...]</pre></p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p style="padding-left: 30px;">-a, --amend  修改现有清单列表<br />--insecure 支持和不安全仓库通信</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 你需要指定一组成员镜像，来创建清单列表
#                      清单列表名字，任何平台的客户端都使用该名字拉取镜像
docker manifest create docker.gmem.cc/coolapp:v1 \
    docker.gmem.cc/coolapp-ppc64le-linux:v1 \
    docker.gmem.cc/coolapp-arm-linux:v1 \
    docker.gmem.cc/coolapp-amd64-linux:v1 \
    docker.gmem.cc/coolapp-amd64-windows:v1 </pre>
</td>
</tr>
<tr>
<td class="blog_h3">manifest annotate</td>
<td>
<p>为本地镜像清单标注额外的信息，设置清单列表中的某个清单对应的体系结构、操作系统
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker manifest annotate [OPTIONS] MANIFEST_LIST MANIFEST</pre></p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p style="padding-left: 30px;">--arch 标注架构<br />--variant 标注架构变体<br />--os 标注操作系统<br />--os-features 标注操作系统特性</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 将清单列表中的某个镜像的体系结构标注为arm
docker manifest annotate docker.gmem.cc/coolapp:v1 \
  docker.gmem.cc/coolapp-arm-linux --arch arm</pre>
</td>
</tr>
<tr>
<td class="blog_h3">manifest push</td>
<td>
<p>将镜像清单列表推送到仓库
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">docker manifest push [OPTIONS] MANIFEST_LIST</pre></p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<p style="padding-left: 30px;">-p, --purge 推送后删除本地清单<br />--insecure 支持和不安全仓库通信</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag">docker manifest push docker.gmem.cc/coolapp:v1 </pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">docker-machine</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">active</td>
<td>显示哪些宿主机是活动的</td>
</tr>
<tr>
<td class="blog_h3">config</td>
<td>打印一个宿主机的连接配置</td>
</tr>
<tr>
<td class="blog_h3">create</td>
<td>创建一个宿主机</td>
</tr>
<tr>
<td class="blog_h3">env</td>
<td>打印用于准备连接到目标宿主机的Shell命令</td>
</tr>
<tr>
<td class="blog_h3">inspect</td>
<td>显示一个宿主机的详细信息</td>
</tr>
<tr>
<td class="blog_h3">ip</td>
<td>获取一个宿主机的IP地址</td>
</tr>
<tr>
<td class="blog_h3">kill</td>
<td>杀死一个宿主机</td>
</tr>
<tr>
<td class="blog_h3">provision</td>
<td>重新provision现有的宿主机</td>
</tr>
<tr>
<td class="blog_h3">regenerate-certs</td>
<td>为一个宿主机准备TLS证书</td>
</tr>
<tr>
<td class="blog_h3">restart</td>
<td>重启一个宿主机</td>
</tr>
<tr>
<td class="blog_h3">rm</td>
<td>移除一个宿主机</td>
</tr>
<tr>
<td class="blog_h3">ssh</td>
<td>通过SSH登录宿主机或者执行命令</td>
</tr>
<tr>
<td class="blog_h3">scp</td>
<td>在宿主机之间复制文件</td>
</tr>
<tr>
<td class="blog_h3">start</td>
<td>启动一个宿主机</td>
</tr>
<tr>
<td class="blog_h3">status</td>
<td>获取宿主机状态</td>
</tr>
<tr>
<td class="blog_h3">stop</td>
<td>停止一个宿主机</td>
</tr>
<tr>
<td class="blog_h3">upgrade</td>
<td>升级宿主机的Docker到最新版本</td>
</tr>
<tr>
<td class="blog_h3">url</td>
<td>获取一个宿主机的URL</td>
</tr>
<tr>
<td class="blog_h3">version</td>
<td>查看DM的版本信息</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">docker-compose</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">build</td>
<td>构建或者重新构建服务</td>
</tr>
<tr>
<td class="blog_h3">bundle</td>
<td>从Compose创建一个Docker分布式应用程序束（Distributed Application Bundle，ADB）</td>
</tr>
<tr>
<td class="blog_h3">config</td>
<td>验证并查看Compose文件</td>
</tr>
<tr>
<td class="blog_h3">create</td>
<td>创建服务</td>
</tr>
<tr>
<td class="blog_h3">down</td>
<td>停止并移除容器、网络、镜像、卷</td>
</tr>
<tr>
<td class="blog_h3">events</td>
<td>从容器接收实时事件</td>
</tr>
<tr>
<td class="blog_h3">images</td>
<td>列出镜像</td>
</tr>
<tr>
<td class="blog_h3">kill</td>
<td>杀死容器</td>
</tr>
<tr>
<td class="blog_h3">logs</td>
<td>查看容器的日志</td>
</tr>
<tr>
<td class="blog_h3">pause</td>
<td>暂停服务</td>
</tr>
<tr>
<td class="blog_h3">port</td>
<td>打印一个端口绑定的公共端口</td>
</tr>
<tr>
<td class="blog_h3">ps</td>
<td>列出容器</td>
</tr>
<tr>
<td class="blog_h3">pull</td>
<td>拉取服务镜像</td>
</tr>
<tr>
<td class="blog_h3">push</td>
<td>推送服务镜像</td>
</tr>
<tr>
<td class="blog_h3">restart</td>
<td>重启服务</td>
</tr>
<tr>
<td class="blog_h3">rm</td>
<td>移除停止的容器</td>
</tr>
<tr>
<td class="blog_h3">run</td>
<td>运行一次性命令</td>
</tr>
<tr>
<td class="blog_h3">scale</td>
<td>扩充或减小一个服务的容器数量</td>
</tr>
<tr>
<td class="blog_h3">stop</td>
<td>停止服务</td>
</tr>
<tr>
<td class="blog_h3">top</td>
<td>显示运行中的进程</td>
</tr>
<tr>
<td class="blog_h3">unpause</td>
<td>从暂停中恢复服务</td>
</tr>
<tr>
<td class="blog_h3">up</td>
<td>创建并启动容器</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">dockerd</span></div>
<div class="blog_h3"><span class="graybg">建议参数</span></div>
<p><pre class="crayon-plain-tag">{
  "live-restore": true,
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5"
  }
}</pre>
<div class="blog_h1"><span class="graybg">容器网络</span></div>
<div class="blog_h2"><span class="graybg">容器网络模型CNM</span></div>
<p>Docker通过libnetwork实现了此模型，CNM的架构如下：</p>
<p><img class="aligncenter  wp-image-27163" src="https://blog.gmem.cc/wp-content/uploads/2015/10/cnm.png" alt="cnm" width="905" height="416" /></p>
<p>其中：</p>
<ol>
<li>网络沙盒：提供容器的网络栈，包括网络接口、路由表、DNS配置、Iptables等。实现技术包括Linux网络命名空间、FreeBSD Jail等</li>
<li>每个沙盒中可以包含来自多个网络的端点（Endpoint），端点是连接网络沙盒和后端网络的桥梁，实现技术包括Veth对、tap/tun设备、OVS内部端口等</li>
<li>容器网络（Backend Network）：一组可以相互通信的端点的集合。对应的实现技术可以是Linux Bridge、VLAN等</li>
</ol>
<p>此外，CNM还依赖于另外两个对象来完成Docker网络管理：</p>
<ol>
<li>Network Controller：对外提供分配、管理网络的APIs</li>
<li>Drivers：负责一种网络的管理，包括IPAM</li>
</ol>
<p>CNM的原生实现是Libnetwork，是Docker团队从Docker核心中分离出来的网络相关功能。</p>
<p>默认情况下，Docker使用Veth对的方案：</p>
<ol>
<li>创建一个docker0网桥，如果没有容器运行，则此网桥是down状态</li>
<li>创建一个Veth对，一端接到网桥docker0，另外一端放在容器网络命名空间中，作为eth0</li>
<li>属于同一网络的容器，都会连接到docker0，因此可以相互通信</li>
</ol>
<div class="blog_h2"><span class="graybg">组网方案</span></div>
<div class="blog_h3"><span class="graybg">隧道方案</span></div>
<p>隧道网络也叫overlay网络，在IaaS网络中就被大量使用。</p>
<p>优势：几乎不依赖基础网络架构，只要3层互联即可。</p>
<p>劣势：</p>
<ol>
<li>随着节点规模的增加，复杂度随之升高</li>
<li>封包二次包装，网络问题定位麻烦，而且影响性能</li>
</ol>
<p>基于覆盖网络的插件包括：</p>
<ol>
<li>Weave，基于VXLAN</li>
<li>Open vSwitch（<span style="background-color: #c0c0c0;">OVS</span>）：基于VXLAN + GRE，<span style="background-color: #c0c0c0;">性能方面损失较为严重</span></li>
<li>flannel：支持自研UDP封包（性能较差，性能损失50%+），以及Linux内核的VXLAN（性能损失20-30%）</li>
</ol>
<div class="blog_h3"><span class="graybg">路由方案</span></div>
<p>隧道方案解决的问题是，主机之间无法直接传递容器IP的封包。如果解决路由的问题，就可以避免二次包装的性能损失。</p>
<p>基于路由的方案包括：</p>
<ol>
<li>Calico，基于BGP，支持细致的ACL控制，混合云友好</li>
<li>Macvlan，隔离性好，性能最优，需要二层网络，大多数云服务商不支持，难以实现混合云</li>
<li>Metaswitch，容器内部分配一个路由只想宿主机地址，性能接近原生</li>
</ol>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">如何获取docker容器的runc配置文件</span></div>
<p>位置在：/run/containerd/io.containerd.runtime.v1.linux/moby/$CONTAINER_ID/config.json</p>
<div class="blog_h3"><span class="graybg">如果进入容器的/tmp目录</span></div>
<p>如果容器正在运行，可以通过nsenter进入。</p>
<p>如果容器已经死掉，可以通过命令：</p>
<pre class="crayon-plain-tag">docker inspect 3b7227ffdb88 | grep UpperDir</pre>
<p>得到UpperDir，然后进入UpperDir，即可在tmp子目录中看到容器修改后的/tmp目录的内容。 </p>
<div class="blog_h3"><span class="graybg">dockerd日志在哪</span></div>
<p>老版本Ubuntu：<pre class="crayon-plain-tag">/var/log/upstart/docker.log</pre></p>
<p>使用Systemd的：<pre class="crayon-plain-tag">sudo journalctl -fu docker.service</pre></p>
<p>CentOS：<pre class="crayon-plain-tag">/var/log/daemon.log | grep docker</pre></p>
<div class="blog_h3"><span class="graybg">同时监听TCP/UDS</span></div>
<pre class="crayon-plain-tag">DOCKER_OPTS=" -H 10.0.0.1:2376 -H unix:///var/run/docker.sock"
# 测试 docker -H 10.0.0.1:2376 ps</pre>
<div class="blog_h3"><span class="graybg">使用代理-容器</span></div>
<p>要让启动的容器自动设置代理环境变量，可以：</p>
<pre class="crayon-plain-tag">{
    "proxies": {
        "default": {
                "httpProxy": "http://10.0.0.1:8087",
                "httpsProxy": "http://10.0.0.1:8087",
                "noProxy": "localhost,127.0.0.1,172.21.*,172.17.*,172.27.*,192.168.*,10.0.*"
            }
        }
    }
}</pre>
<p>然后启动容器，你就可以看到对应的环境变量了.</p>
<div class="blog_h3"><span class="graybg">使用代理-守护进程</span></div>
<p>docker pull等操作是发生在守护进程中的，需要按此节的说明设置</p>
<p>必须配置Docker的配置文件，命令行直接设置代理无效：</p>
<pre class="crayon-plain-tag"># Ubuntu适用
export http_proxy="http://10.0.0.1:8088"
export https_proxy="http://10.0.0.1:8088"</pre>
<p>如果使用Fedora，则：</p>
<pre class="crayon-plain-tag">mkdir -p /etc/systemd/system/docker.service.d</pre><br />
<pre class="crayon-plain-tag">[Service]
Environment="HTTP_PROXY=http://10.0.0.1:8088/"</pre>
<p> 刷出变更并重启服务：</p>
<pre class="crayon-plain-tag">systemctl daemon-reload
systemctl restart docker</pre>
<div class="blog_h3"><span class="graybg">报image not found</span></div>
<p>可能原因是没有登陆到私服</p>
<div class="blog_h3"><span class="graybg">报证书过期</span></div>
<p>报错信息：certificate has expired or is not yet valid docker</p>
<p>如果测试wget/curl等命令没问题，可能是由于Docker使用的代理导致。</p>
<div class="blog_h3"><span class="graybg">docker-proxy占用CPU高</span></div>
<p>由于NAT（Docker端口映射）导致。默认情况下Docker使用用于空间代理docker-proxy来处理NAT，性能较低。可以设置dockerd参数禁用：</p>
<pre class="crayon-plain-tag">dockerd ... --userland-proxy=false</pre>
<div class="blog_h3"><span class="graybg">挂载卷性能差</span></div>
<p>Mac OS下可能存在此问题，解决方案：</p>
<pre class="crayon-plain-tag">docker run -v /Users/alex/project:/project:cached alpine command</pre>
<div class="blog_h3"><span class="graybg">x509: certificate signed by unknown authority</span></div>
<p>进行docker login时出现此错误，可以把目标仓库的证书拷贝到<pre class="crayon-plain-tag">/etc/docker/certs.d/</pre>下，以仓库域名为子目录。</p>
<p>如果使用Let's Encrypt签名的证书，务必使用完整证书链。</p>
<div class="blog_h3"><span class="graybg">iptables: No chain/target/match by that name docker</span></div>
<p>在CentOS下，启动firewalld后，新创建Docker容器并进行端口映射会出现此错误。重启Docker即可。</p>
<div class="blog_h2"><span class="graybg">网络命名空间</span></div>
<div class="blog_h3"><span class="graybg">无法在宿主机看到</span></div>
<p>如果无法在宿主机上通过<pre class="crayon-plain-tag">ip netns list</pre>看到容器的网络命名空间，可以调用命令：</p>
<pre class="crayon-plain-tag">ln -s /var/run/docker/netns  /var/run/netns  </pre>
<div class="blog_h2"><span class="graybg">内核参数调优</span></div>
<div class="blog_h3"><span class="graybg">--sysctl</span></div>
<p>从1.12开始支持，该参数可以直接传递sysctl变量：</p>
<pre class="crayon-plain-tag">docker run --sysctl net.ipv4.ip_forward=1</pre>
<p>用于配置命名空间化的内核参数（namespaced kernel parameters）。 </p>
<p>注意，仅仅被<span style="background-color: #c0c0c0;">Docker加入白名单</span>的内核参数可以调整，否则你会收到错误：sysctl '***' is not whitelisted。白名单包括：</p>
<ol>
<li>kernel.sem、kernel.shmall、kernel.shmmax、kernel.shmmni、kernel.shm_rmid_forced</li>
<li>fs.mqueue.***</li>
<li>net.***</li>
</ol>
<p>此外，被调整的内核参数还必须是：</p>
<ol>
<li>在容器中可见，例如net.core.rmem_max</li>
<li>支持命名空间化，有些配置是全局的，不支持命名空间化</li>
</ol>
<div class="blog_h3"><span class="graybg">网络参数调优</span></div>
<p>如果使用--net=host，则使用宿主机的网络栈，直接使用宿主机的内核参数，不需要特殊操作。</p>
<div class="blog_h2"><span class="graybg">无法启动守护进程</span></div>
<div class="blog_h3"><span class="graybg">msg="[graphdriver] prior storage driver aufs failed: driver not supported"</span></div>
<pre class="crayon-plain-tag">sudo rm /var/lib/docker/aufs -rf</pre>
<div class="blog_h2"><span class="graybg">无法访问外部网络</span></div>
<p>原因未知，可能和Docker的NAT存在问题，重启Docker后解决。</p>
<div class="blog_h2"><span class="graybg"><a id="lvs-related-rst"></a>LVS相关的RST</span></div>
<div class="blog_h3"><span class="graybg">症状</span></div>
<p>访问某通过LVS暴露的接口，20%几率出现Connection Reset By Peer。进一步检查发现：</p>
<ol>
<li>此问题和Docker本身无关，但凡客户端通过NAT访问的，都有几率出现</li>
<li>curl测试，有几率出现卡死，但是目标接口速度很快</li>
</ol>
<div class="blog_h3"><span class="graybg">分析</span></div>
<p>当卡死时，最终提示：curl: (56) Recv failure: Connection timed out，Docker宿主机抓包如下：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2015/10/rst-timeout.png"><img class="aligncenter  wp-image-22571" src="https://blog.gmem.cc/wp-content/uploads/2015/10/rst-timeout.png" alt="rst-timeout" width="923" height="183" /></a></p>
<p>可以看到发送HTTP请求后，出现重传，随后即RST。LVS发来的TCP Dup ACK中的SLE、SRE（选择性ACK时，已经Ack的字节范围，提示对方不再传输这些范围的数据）很不正常。在客户端禁用SACK后，问题消失：</p>
<pre class="crayon-plain-tag">net.ipv4.tcp_sack = 0
net.ipv4.tcp_dsack = 0
net.ipv4.tcp_fack = 0 </pre>
<p>值得注意的是：在NATed的客户端来看，是服务器主动RST，在NAT设备来看，是它主动RST，并谎报给NATed客户端说服务器进行了重置。</p>
<div class="blog_h2"><span class="graybg">构建镜像时的问题</span></div>
<div class="blog_h3"><span class="graybg">构建镜像时Dockerfile中的apt-get命令时报错：<span style="color: #242729;">Could not resolve 'archive.ubuntu.com'</span></span></div>
<p>可能是DNS配置错误导致的，执行以下步骤修复： </p>
<ol>
<li>修改/etc/default/docker，设置正确的Docker守护程序选项，例如：<pre class="crayon-plain-tag">DOCKER_OPTS="--dns 178.79.131.110 --dns 8.8.8.8"</pre> </li>
<li>重启Docker守护程序：<pre class="crayon-plain-tag">sudo service docker restart</pre> </li>
<li>重新构建，指定no-cache选项，强制Docker镜像重新获取DNS：<pre class="crayon-plain-tag">docker build --no-cache=true ...</pre>  </li>
</ol>
<div class="blog_h3"><span class="graybg">时区如何设置</span></div>
<pre class="crayon-plain-tag">RUN rm /etc/localtime &amp;&amp; ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime</pre>
<p>部分基础镜像没有/usr/share/zoneinfo目录，需要手工拷贝/etc/localtime文件</p>
<div class="blog_h3"><span class="graybg">如何编写入口点脚本</span></div>
<p>两个简单原则：</p>
<ol>
<li>能够处理挂断信号。需要注意，docker stop / docker kill只会向PID为1的容器进程发送信号</li>
<li>能够阻止脚本退出</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag">#!/usr/bin/env bash

# 捕获挂断信号，执行清理
sighdl() {
    service myservice stop
    TERM_FLAG=1
}
trap sighdl HUP INT PIPE QUIT TERM

# 启动服务
service myservice start

# 防止脚本退出的命令
while [ "$TERM_FLAG" != "1" ] ; do :; done</pre>
<p>如果防止脚本退出的命令调用了其它程序，例如：<pre class="crayon-plain-tag">tail -f /var/log/myservice.log</pre>，会出现一个子进程，虽然上述脚本的PID为1，但是子进程不会收到信号，因而容器仍然卡在那不会退出。</p>
<p>一个生产环境中入口点脚本的例子：<a href="/webrtc-server-basedon-kurento#entrypoint">Kurento的入口点脚本</a></p>
<div class="blog_h3"><span class="graybg">如何扁平化镜像</span></div>
<p>层次过多的文件系统，不但访问效率较低，而且占用更大的磁盘空间。可以export容器并import为镜像，这样产生的镜像只有一个层次：</p>
<pre class="crayon-plain-tag">docker export container-name | docker import - image:tag </pre>
<div class="blog_h2"><span class="graybg">Swarm模式相关问题</span></div>
<div class="blog_h3"><span class="graybg">节点运行服务报错：No such image</span></div>
<p>在管理节点上预先登录到Docker Hub或者私服，然后：</p>
<pre class="crayon-plain-tag">docker service create --with-registry-auth   # 添加该选项
  --replicas 10 --network overlay --name ping docker.gmem.cc/ubuntu:14.04 ping 10.0.0.1</pre>
<div class="blog_h2"><span class="graybg">运行容器时的问题</span></div>
<div class="blog_h3"><span class="graybg">如何进入无法启动的容器</span></div>
<p>有些情况下，因为配置错误，容器启动后立即退出。如果想通过Shell交互式的查问题，可以：</p>
<pre class="crayon-plain-tag"># 提交为临时镜像
docker commit mongo-c6 tempimg

# 使用Bash作为入口点进入
docker run --entrypoint=bash -it --rm  tempimg

# 解决完问题后，删除临时镜像
docker rmi tempimg</pre>
<div class="blog_h3"><span class="graybg">如何删除孤儿卷</span></div>
<pre class="crayon-plain-tag"># 列出孤儿卷
docker volume ls -qf dangling=true

# 删除所有孤儿卷
docker volume rm $(docker volume ls -qf dangling=true)</pre>
<div class="blog_h3"><span class="graybg">挂载卷的权限问题</span></div>
<p>使用<pre class="crayon-plain-tag">docker -v</pre>命令把宿主机上不存在的目录挂载到容器指定位置时：</p>
<ol>
<li>自动以root身份创建不存在的目录</li>
<li>容器中目录的所有者为root，即使目录本来存在（而被覆盖）且所有者不是root</li>
</ol>
<p>这种行为会导致潜在的无权访问的问题。</p>
<p>解决办法：容器中的用户，和宿主机的用户，以ID对应。因此，你可以预先创建宿主机目录，并chown给容器中需要使用此目录的用户的ID。 </p>
<div class="blog_h3"><span class="graybg">无法捕获信号</span></div>
<p>最好确保你的应用程序的PID为1，如果Entrypoint Spawn的子进程中运行应用程序，则Entrypoint必须能够捕获信号并执行适当的命令来停止应用程序进程。</p>
<p>无法捕获信号的常见原因：</p>
<ol>
<li>使用了Shell风格的Entrypoint，这种情况下，入口点变为/bin/sh的子进程，无法收到信号。你需要：<br />
<pre class="crayon-plain-tag"># 不要用这种形式
ENTRYPOINT "/entrypoint.sh" arg1 arg2
# 用下面的形式
ENTRYPOINT ["/entrypoint.sh", "arg1", "arg2"]</pre>
</li>
<li>入口点调用了应用程序，但是没有替换当前进程。你需要以exec方式调用目标应用程序，例如：<br />
<pre class="crayon-plain-tag">exec /jdk/bin/java $JAVA_OPTS -cp app.jar $JAVA_MAIN_CLASS $JAVA_MAIN_ARGS</pre>
</li>
<li>
<p>虽然使用exec替换进程，但是不是替换入口点进程。例如引入了管道：</p>
<pre class="crayon-plain-tag"># 这会导致在子Shell中进行
exec java | grep ... </pre>
</li>
<li>可能没有捕获信号并正确处理</li>
</ol>
<div class="blog_h2"><span class="graybg">Ubuntu相关</span></div>
<div class="blog_h3"><span class="graybg">升级到CE 18后无法启动容器</span></div>
<p>报错：OCI runtime create failed: container_linux.go:348: starting container process caused "process_linux.go:297: copying bootstrap data to pipe caused \"write init-p: broken pipe\"": unknown
<p>解决办法：</p>
<pre class="crayon-plain-tag">sudo apt-get install docker-ce=18.06.1~ce~3-0~ubuntu</pre>
<div class="blog_h2"><span class="graybg">Alpine相关</span></div>
<div class="blog_h3"><span class="graybg"> exec user process caused no such file or directory</span></div>
<p>以Alpine作为基础镜像，运行动态链接的Go应用程序，出现此报错。</p>
<p>原因是，应用程序动态链接到了 GNU libc。Alpine的musl libc库提供了对GNU libc的部分兼容性，可用于尝试解决此问题：</p>
<pre class="crayon-plain-tag">apk add libc6-compat</pre>
<div class="blog_h2"><span class="graybg">CentOS相关</span></div>
<div class="blog_h3"><span class="graybg">配置文件和命令</span></div>
<p>Docker配置信息可能位于：</p>
<ol>
<li>/usr/lib/systemd/system/docker.service</li>
</ol>
<p>Docker服务相关命令：</p>
<pre class="crayon-plain-tag">systemctl daemon-reload
systemctl restart docker </pre>
<div class="blog_h2"><span class="graybg">软件包缺失的问题</span></div>
<div class="blog_h3"><span class="graybg">add-apt-repository</span></div>
<pre class="crayon-plain-tag">apt-get install software-properties-common</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/docker-study-note">Docker学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/docker-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>KVM和QEMU学习笔记</title>
		<link>https://blog.gmem.cc/kvm-qemu-study-note</link>
		<comments>https://blog.gmem.cc/kvm-qemu-study-note#comments</comments>
		<pubDate>Mon, 17 Aug 2015 06:22:32 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Virtualization]]></category>
		<category><![CDATA[libvirt]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=12816</guid>
		<description><![CDATA[<p>简介 虚拟化基础知识 关于Hypervisor Hypervisor，即虚拟机监管程序（virtual machine monitor ，VMM）。它可以是电脑上的软件、固件或者硬件，用于建立和执行虚拟机。拥有Hypervisor后，你可以执行一个或者多个虚拟机。这些虚拟机称为客户机（guest machine），相应的Hypervisor所在机器称为宿主机（host machine）。 传统的虚拟化技术都是基于Hypervisor的，它们被分为两类： bare-metal Hypervisor：裸机监管程序，直接运行在硬件上 Hosted Hypervisor：被宿主监管程序，Hypervisor运行在操作系统之上，就像一个应用程序一样 特权级别和虚拟化类型 X86处理器定义了定义了0-3个特权级，数字越小，权限越高。 对于Linux来说，在没有虚拟化的情况下，内核态对应了0级，用户态对应3级。 传统的虚拟化技术都是在宿主机、客户机之间加一个Hypervisor。因此，当在Linux上运行Linux虚拟机时，两个内核都需要运行在0级。根据解决此冲突（对于Host来说整个Client是用户程序）的方式的不同，虚拟化被分为3种类型： 虚拟化类型 说明 半虚拟化 Paravirtualization。此类型的特点是： <a class="read-more" href="https://blog.gmem.cc/kvm-qemu-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/kvm-qemu-study-note">KVM和QEMU学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<div class="blog_h2"><span class="graybg">虚拟化基础知识</span></div>
<div class="blog_h3"><span class="graybg">关于Hypervisor</span></div>
<p>Hypervisor，即虚拟机监管程序（virtual machine monitor ，VMM）。它可以是电脑上的软件、固件或者硬件，用于建立和执行虚拟机。拥有Hypervisor后，你可以执行一个或者多个虚拟机。这些虚拟机称为客户机（guest machine），相应的Hypervisor所在机器称为宿主机（host machine）。</p>
<p>传统的虚拟化技术都是基于Hypervisor的，它们被分为两类：</p>
<ol>
<li>bare-metal Hypervisor：裸机监管程序，直接运行在硬件上</li>
<li>Hosted Hypervisor：被宿主监管程序，Hypervisor运行在操作系统之上，就像一个应用程序一样</li>
</ol>
<div class="blog_h3"><span class="graybg">特权级别和虚拟化类型</span></div>
<p>X86处理器定义了定义了0-3个特权级，数字越小，权限越高。</p>
<p>对于Linux来说，在没有虚拟化的情况下，内核态对应了0级，用户态对应3级。</p>
<p>传统的虚拟化技术都是在宿主机、客户机之间加一个Hypervisor。因此，当在Linux上运行Linux虚拟机时，两个内核都需要运行在0级。根据解决此冲突（对于Host来说整个Client是用户程序）的方式的不同，虚拟化被分为3种类型：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">虚拟化类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>半虚拟化</td>
<td>
<p>Paravirtualization。此类型的特点是：</p>
<ol>
<li>Hypervisor运行在内核态，对应0级特权</li>
<li>客户机的内核不运行在内核态，内核被修改，需要在0级执行的特权指令转调Hypervisor</li>
<li>客户机上的用户程序运行在3级</li>
</ol>
</td>
</tr>
<tr>
<td>非硬件辅助全虚拟化</td>
<td>
<p>Full Virtualization without Hardware Assist。此类型的特点是：</p>
<ol>
<li>Hypervisor运行在内核态</li>
<li>Hypervisor位客户机提供一个模拟的CPU</li>
<li>客户机内核不需要修改，运行在模拟CPU上的0级</li>
<li>Hypervisor对客户机的CPU指令进行转译，变成正式CPU的指令</li>
</ol>
</td>
</tr>
<tr>
<td>硬件辅助全虚拟化</td>
<td>
<p>Full Virtualization with Hardware Assist。由Intel VT或AMD-V实现，此类型的特点是：</p>
<ol>
<li>Hypervisor运行在新的-1级别</li>
<li>客户机内核直接运行在真实CPU的0级</li>
</ol>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">KVM</span></div>
<p>KVM，即基于内核的虚拟机（Kernel-based Virtual Machine），是构建于支持虚拟化扩展（Intel VT 或者 AMD-V）的x<span style="background-color: #c0c0c0;">86平台</span>、<span style="background-color: #c0c0c0;">Linux</span>操作系统之上的，完整虚拟化解决方案。使用KVM，你可以运行多个Linux或者Windows系统镜像，这些虚拟机拥有私有的虚拟化设备，包括网卡、磁盘、显卡等。</p>
<p>KVM主要包含两个内核组件：</p>
<ol>
<li>可加载的内核模块 kvm.ko，负责核心的虚拟化基础功能</li>
<li>针对处理器的模块：kvm-intel.ko或者kvm-amd.ko</li>
</ol>
<p>从2.6.20版本的Linux内核开始，KVM的内核组件就被包含在其中。从<a href="http://wiki.qemu.org/Main_Page">QEMU</a> 1.3开始，KVM的用户空间组件被包含在其中。要查看你的机器是否支持KVM，可以执行：<pre class="crayon-plain-tag">lsmod | grep kvm</pre> </p>
<div class="blog_h2"><span class="graybg">QEMU</span></div>
<p>QEMU是一个基于<span style="background-color: #c0c0c0;">通用目的</span>开源仿真器/虚拟器软件。它可以模拟：</p>
<ol>
<li>CPU</li>
<li>Intel e1000 PCI等网卡</li>
<li>基于PCI IDE接口的硬盘、光驱</li>
<li>软驱</li>
<li>串口</li>
<li>AC97兼容声卡以及其它声卡</li>
<li>PS/2 键盘鼠标</li>
<li>VGA显卡</li>
</ol>
<p>等多种外围设备。QEMU最多支持255 CPU的SMP。</p>
<p>当作为<span style="background-color: #c0c0c0;">仿真器（Emulator，模拟器）</span>使用时，它可以在真实机器（例如你的x86_64台式机）上模拟一台机器 + 操作系统 + 程序，而这台模拟的机器的体系结构（例如ARM板）与宿主机器不同。</p>
<p>而作为<span style="background-color: #c0c0c0;">虚拟器（Virtualizer）</span>使用时，它可以在宿主机器上直接执行客户机（虚拟机）的代码，因而虚拟机的性能接近于宿主机。QEMU通过下列方式之一来支持虚拟化：</p>
<ol>
<li>在XEN监管程序（Hypervisor）之上执行</li>
<li>在支持KVM的Linux操作系统下运行。使用KVM时QEMU可以虚拟化x86、PowerPC、S390客户机</li>
</ol>
<p>注意Emulator和Virtualizer的区别，最重要的一点是客户机的代码是直接执行（意味着宿主和客户机体系结构兼容），还是模拟执行，后者的效率要低得多。很多同学喜欢在PC上玩街机游戏，这也是通过模拟器（例如Winkawaks）实现的。</p>
<p>和Vmware、VirtualBox 之类的虚拟机管理软件不同，QEMU不提供图形化的管理界面。你可以使用第三方的图形前端，例如qtemu，但是命令行的丰富性让QEMU更适合在服务器上使用。</p>
<div class="blog_h3"><span class="graybg">QEMU与KVM</span></div>
<p>单纯靠QEMU来模拟一系列硬件，因为存在指令转译，性能一般很差。而KVM可以基于Intel-VT、AMD-V实现硬件辅助的CPU虚拟化，客户机指令直接在真实CPU上运行。因此结合KVM可以很好的提高QEMU的<span style="background-color: #c0c0c0;">CPU性能</span>。另一方面KVM仅仅提供CPU的虚拟化，它无法构建一台完整的虚拟机。因此QEMU和KVM整合的需求就很明显了。</p>
<p>qemu-kvm项目就是来整合QEMU和KVM的，此项目在1.3.0版本开始正式合并到QEMU项目的master上。qemu-kvm（qemu-system-***）利用ioctl调用/dev/kvm，将有关CPU的部分交由KVM去做。</p>
<p>如果KVM内核模块存在、且CPU支持，你可以通过下面的选项启用KVM支持：</p>
<pre class="crayon-plain-tag"># 启用基于KVM的虚拟化加速
-enable-kvm</pre>
<div class="blog_h3"><span class="graybg">周围硬件的性能</span></div>
<p>CPU的性能问题解决了，但是QEMU模拟的其它硬件也存在同样的低效问题，于是Virtio被引入了。</p>
<p>Virtio是libvirt的一部分，它是一个关于网络、磁盘等设备的虚拟化标准，在此标准中客户机的设备驱动知道自己运行在虚拟化环境中，因而这些驱动可以和Hypervisor进行直接交互，获得<span style="background-color: #c0c0c0;">接近于Native驱动的性能</span>。</p>
<p>较新版本的Linux发行版都已经把Virtio编译进内核，因而客户机可以直接使用Virtio驱动。</p>
<div class="blog_h2"><span class="graybg">与其它虚拟化技术的比较</span></div>
<div class="blog_h3"><span class="graybg">Xen</span></div>
<p>KVM和XEN都是基于Hypervisor的虚拟化技术。它们的区别包括：</p>
<ol>
<li>Xen<span style="background-color: #c0c0c0;">是裸机监管程序</span>，而KVM某种程度上把Linux内核变成了Hosted Hypervisor</li>
<li>Xen的整体性能高于KVM，但是I/O略差</li>
<li>KVM要求CPU必须支持虚拟化技术，但Xen则没有此限制。这个限制在当前的硬件条件下，基本不是问题</li>
<li>KVM的优势是它对Linux内核的整合程度，KVM本质上就是一个内核模块，因此你可以很容易的升级内核</li>
</ol>
<div class="blog_h3"><span class="graybg">VirtualBox</span></div>
<p>VirtualBox是标准的2类Hypervisor，KVM与它的区别包括：</p>
<ol>
<li>VirtualBox它与商用软件Vmware Workstation一样，都以图形界面为主，适合个人用户。但是在商用环境下，大部分虚拟机都是Headless的（不需要图形界面），此时VirtualBox的GUI则是劣势，GUI浪费了资源</li>
<li>VirtualBox支持大量的宿主操作系统，例如Windows、Linux、Mac OS X。而KVM显然仅支持Linux</li>
<li>一般情况下，轻量级的KVM的性能要比VirtualBox好的多</li>
</ol>
<div class="blog_h3"><span class="graybg">关于LXC</span></div>
<p>LXC即Linux容器（Containers），这是一种<span style="background-color: #c0c0c0;">操作系统层（传统虚拟机是硬件层）</span>虚拟化技术，要由liblxc库及其多语言绑定、一系列控制容器的工具组成。LXC将整个应用，包括：软件本身代码、所需库、支撑软件，打包为一个“容器”。通过Linux内核的特性，可以实现容器与系统之间的隔离，这些特性包括：</p>
<ol>
<li>内核命名空间（IPC、uts、mount、pid、network、user）</li>
<li>Apparmor（限制每个应用程序访问的资源）和SELinux配置</li>
<li>Seccomp（提供应用程序沙盒机制）策略</li>
<li>Chroots（通过pivot_root调用）</li>
<li>cgroups，即控制组（control groups），用来限制、控制、分离进程组的资源（CPU、内存、磁盘等），此特性最初的名字就叫“进程容器”</li>
</ol>
<p>通过LXC，你可以创建尽可能接近标准Linux的环境，同时<span style="background-color: #c0c0c0;">不需要独立的内核</span>。</p>
<p>基于LXC的虚拟化技术和KVM相比，区别如下：</p>
<ol>
<li>LXC的优势在于轻量化和高性能，但是隔离性不高</li>
<li>LXC支持任何体系结构，例如x86、ARM、PowerPC等</li>
</ol>
<div class="blog_h3"><span class="graybg">LXD</span></div>
<p>LXD基于LXC，可以认为是一个Container的Hypervisor，LXD一般创建<span style="background-color: #c0c0c0;">自包含的操作系统用户空间</span>。也就是说，LXD容器内运行的是一个操作系统，虽然存在用户空间和内核空间隔离，但是这个操作系统和宿主系统共享一个内核。</p>
<p>有测试数据表明：LXD相比KVM可以减少50+%的延迟；LXD启动实例的速度比KVM块90+%</p>
<div class="blog_h3"><span class="graybg">Docker</span></div>
<p>近年来非常流行的容器软件，与LXD最大的不同是，Docker打包<span style="background-color: #c0c0c0;">应用程序+自包含的文件系统</span>，而不是操作系统用户空间。每个Docker容器，仅仅包含一个应用程序。曾经Docker也是基于lxc技术的，但是现在它使用自己的库ibcontainer。</p>
<p>Docker中的文件系统、网络都是抽象的，而LXD直接使用宿主机的文件系统、网络，LXD可以方便的设置IP地址。</p>
<p>Docker比起LXD更加轻量，可以实现更高的部署密度。</p>
<div class="blog_h1"><span class="graybg">安装和配置</span></div>
<div class="blog_h2"><span class="graybg">使用随系统自带的KVM</span></div>
<p>大部分的Linux发行版已经内置了KVM内核模块以及用户空间工具，使用这些内置组件是最容易、推荐的方式：</p>
<ol>
<li>KVM内核模块现在是Linux内核的一部分，除非你使用的是精简过的内核</li>
<li>用户空间组件，软件包名称一般是qemu-kvm或者kvm，例如：
<ol>
<li>Ubuntu下可以执行<pre class="crayon-plain-tag">apt-get install qemu-kvm</pre> 安装</li>
<li>CentOS下可以执行<pre class="crayon-plain-tag">yum install kvm</pre>安装</li>
</ol>
</li>
<li>客户机驱动：Linux客户机的驱动包含在内核中；<a href="http://www.linux-kvm.org/page/WindowsGuestDrivers/Download_Drivers">Windows客户机的驱动</a>需要下载</li>
</ol>
<div class="blog_h2"><span class="graybg">手工构建KVM</span></div>
<p>安装QEMU的依赖包：</p>
<pre class="crayon-plain-tag">sudo apt-get install gcc libsdl1.2-dev zlib1g-dev libasound2-dev linux-kernel-headers pkg-config libgnutls-dev libpci-dev</pre>
<p>下载用户空间组件： </p>
<ol>
<li>QEMU 1.3或者更老版本的，在<a href="https://sourceforge.net/projects/kvm/files/">Sourceforge</a>下载</li>
<li>新版本，在<a href="http://wiki.qemu.org/Download">QEMU官网</a>下载</li>
</ol>
<p>注意：2.6.29以上版本的内核，可以和任何版本的qemu-kvm搭配使用。</p>
<div class="blog_h3"><span class="graybg">构建和安装用户空间组件</span></div>
<pre class="crayon-plain-tag">tar xzf qemu-kvm-release.tar.gz
cd qemu-kvm-release
./configure --prefix=/usr/local/kvm
make
sudo make install</pre>
<div class="blog_h3"><span class="graybg">构建和安装KVM内核模块</span></div>
<p>如果你使用旧版本内核，或者内核精简了KVM，则需要此步骤：</p>
<pre class="crayon-plain-tag">tar xjf kvm-kmod-release.tar.bz2
cd kvm-kmod-release 
./configure
make 
sudo make install</pre>
<div class="blog_h3"><span class="graybg">启用内核模块</span></div>
<pre class="crayon-plain-tag"># 对于Intel CPU
sudo /sbin/modprobe kvm-intel
# 对于AMD CPU
sudo /sbin/modprobe kvm-amd</pre>
<div class="blog_h2"><span class="graybg">构建QEMU</span></div>
<p>从3.0.0开始QEMU的版本大跃进，每年major版本增加1，目前已经是5.x版本。</p>
<div class="blog_h3"><span class="graybg">下载和构建</span></div>
<pre class="crayon-plain-tag">wget https://download.qemu.org/qemu-5.1.0.tar.xz
tar xvJf qemu-5.1.0.tar.xz 
cd qemu-5.1.0/
./configure
make</pre>
<div class="blog_h3"><span class="graybg">配置项说明</span></div>
<pre class="crayon-plain-tag">./configure --help

# 方括号中是默认值

Standard options:
  --prefix=PREFIX          install in PREFIX [/usr/local]
  --interp-prefix=PREFIX   where to find shared libraries, etc.
                           use %M for cpu name [/usr/gnemul/qemu-%M]
  # 目标列表，默认所有
  # xxx-softmmu 生成qemu-system-xxx，用于运行xxx架构下的虚拟机
  # xxx-linux-user 生成qemu-xxx，用于模拟运行xxx架构下的应用程序，可以配合binfmt_misc和Docker联用
  --target-list=LIST       set target list (default: build everything)
                           Available targets: aarch64-softmmu alpha-softmmu 
                           arm-softmmu avr-softmmu cris-softmmu hppa-softmmu 
                           i386-softmmu lm32-softmmu m68k-softmmu 
                           microblazeel-softmmu microblaze-softmmu 
                           mips64el-softmmu mips64-softmmu mipsel-softmmu 
                           mips-softmmu moxie-softmmu nios2-softmmu 
                           or1k-softmmu ppc64-softmmu ppc-softmmu 
                           riscv32-softmmu riscv64-softmmu rx-softmmu 
                           s390x-softmmu sh4eb-softmmu sh4-softmmu 
                           sparc64-softmmu sparc-softmmu tricore-softmmu 
                           unicore32-softmmu x86_64-softmmu xtensaeb-softmmu 
                           xtensa-softmmu aarch64_be-linux-user 
                           aarch64-linux-user alpha-linux-user armeb-linux-user 
                           arm-linux-user cris-linux-user hppa-linux-user 
                           i386-linux-user m68k-linux-user 
                           microblazeel-linux-user microblaze-linux-user 
                           mips64el-linux-user mips64-linux-user 
                           mipsel-linux-user mips-linux-user 
                           mipsn32el-linux-user mipsn32-linux-user 
                           nios2-linux-user or1k-linux-user 
                           ppc64abi32-linux-user ppc64le-linux-user 
                           ppc64-linux-user ppc-linux-user riscv32-linux-user 
                           riscv64-linux-user s390x-linux-user sh4eb-linux-user 
                           sh4-linux-user sparc32plus-linux-user 
                           sparc64-linux-user sparc-linux-user 
                           tilegx-linux-user x86_64-linux-user 
                           xtensaeb-linux-user xtensa-linux-user
  --target-list-exclude=LIST exclude a set of targets from the default target-list

Advanced options (experts only):
  --cross-prefix=PREFIX    use PREFIX for compile tools []
  --cc=CC                  use C compiler CC [cc]
  --iasl=IASL              use ACPI compiler IASL [iasl]
  --host-cc=CC             use C compiler CC [cc] for code run at
                           build time
  --cxx=CXX                use C++ compiler CXX [c++]
  --objcc=OBJCC            use Objective-C compiler OBJCC [cc]
  --extra-cflags=CFLAGS    append extra C compiler flags QEMU_CFLAGS
  --extra-cxxflags=CXXFLAGS append extra C++ compiler flags QEMU_CXXFLAGS
  --extra-ldflags=LDFLAGS  append extra linker flags LDFLAGS
  --cross-cc-ARCH=CC       use compiler when building ARCH guest test cases
  --cross-cc-flags-ARCH=   use compiler flags when building ARCH guest tests
  --make=MAKE              use specified make [make]
  --install=INSTALL        use specified install [install]
  --python=PYTHON          use specified python [/usr/bin/python3]
  --sphinx-build=SPHINX    use specified sphinx-build []
  --smbd=SMBD              use specified smbd [/usr/sbin/smbd]
  --with-git=GIT           use specified git [git]
  --static                 enable static build [no]
  --mandir=PATH            install man pages in PATH
  --datadir=PATH           install firmware in PATH/qemu
  --docdir=PATH            install documentation in PATH/qemu
  --bindir=PATH            install binaries in PATH
  --libdir=PATH            install libraries in PATH
  --libexecdir=PATH        install helper binaries in PATH
  --sysconfdir=PATH        install config in PATH/qemu
  --localstatedir=PATH     install local state in PATH (set at runtime on win32)
  --firmwarepath=PATH      search PATH for firmware files
  --efi-aarch64=PATH       PATH of efi file to use for aarch64 VMs.
  --with-confsuffix=SUFFIX suffix for QEMU data inside datadir/libdir/sysconfdir [/qemu]
  --with-pkgversion=VERS   use specified string as sub-version of the package
  --enable-debug           enable common debug build options
  --enable-sanitizers      enable default sanitizers
  --enable-tsan            enable thread sanitizer
  --disable-strip          disable stripping binaries
  --disable-werror         disable compilation abort on warning
  --disable-stack-protector disable compiler-provided stack protection
  --audio-drv-list=LIST    set audio drivers list:
                           Available drivers: oss alsa sdl pa
  --block-drv-whitelist=L  Same as --block-drv-rw-whitelist=L
  --block-drv-rw-whitelist=L
                           set block driver read-write whitelist
                           (affects only QEMU, not qemu-img)
  --block-drv-ro-whitelist=L
                           set block driver read-only whitelist
                           (affects only QEMU, not qemu-img)
  --enable-trace-backends=B Set trace backend
                           Available backends: dtrace ftrace log simple syslog ust
  --with-trace-file=NAME   Full PATH,NAME of file to store traces
                           Default:trace-&lt;pid&gt;
  --disable-slirp          disable SLIRP userspace network connectivity
  --enable-tcg-interpreter enable TCG with bytecode interpreter (TCI)
  --enable-malloc-trim     enable libc malloc_trim() for memory optimization
  --oss-lib                path to OSS library
  # 为指定CPU构建
  --cpu=CPU                Build for host CPU [x86_64]
  --with-coroutine=BACKEND coroutine backend. Supported options:
                           ucontext, sigaltstack, windows
  --enable-gcov            enable test coverage analysis with gcov
  --gcov=GCOV              use specified gcov [gcov]
  --disable-blobs          disable installing provided firmware blobs
  --with-vss-sdk=SDK-path  enable Windows VSS support in QEMU Guest Agent
  --with-win-sdk=SDK-path  path to Windows Platform SDK (to build VSS .tlb)
  --tls-priority           default TLS protocol/cipher priority string
  --enable-gprof           QEMU profiling with gprof
  --enable-profiler        profiler support
  --enable-debug-stack-usage
                           track the maximum stack usage of stacks created by qemu_alloc_stack
  --enable-plugins
                           enable plugins via shared library loading
  --disable-containers     don't use containers for cross-building
  --gdb=GDB-path           gdb to use for gdbstub tests [/usr/bin/gdb]

Optional features, enabled with --enable-FEATURE and
disabled with --disable-FEATURE, default is enabled if available:

  system          all system emulation targets
  user            supported user emulation targets
  linux-user      all linux usermode emulation targets
  bsd-user        all BSD usermode emulation targets
  docs            build documentation
  guest-agent     build the QEMU Guest Agent
  guest-agent-msi build guest agent Windows MSI installation package
  pie             Position Independent Executables
  modules         modules support (non-Windows)
  module-upgrades try to load modules from alternate paths for upgrades
  debug-tcg       TCG debugging (default is disabled)
  debug-info      debugging information
  sparse          sparse checker
  safe-stack      SafeStack Stack Smash Protection. Depends on
                  clang/llvm &gt;= 3.7 and requires coroutine backend ucontext.

  gnutls          GNUTLS cryptography support
  nettle          nettle cryptography support
  gcrypt          libgcrypt cryptography support
  auth-pam        PAM access control
  sdl             SDL UI
  sdl-image       SDL Image support for icons
  gtk             gtk UI
  vte             vte support for the gtk UI
  curses          curses UI
  iconv           font glyph conversion support
  vnc             VNC UI support
  vnc-sasl        SASL encryption for VNC server
  vnc-jpeg        JPEG lossy compression for VNC server
  vnc-png         PNG compression for VNC server
  cocoa           Cocoa UI (Mac OS X only)
  virtfs          VirtFS
  mpath           Multipath persistent reservation passthrough
  xen             xen backend driver support
  xen-pci-passthrough    PCI passthrough support for Xen
  brlapi          BrlAPI (Braile)
  curl            curl connectivity
  membarrier      membarrier system call (for Linux 4.14+ or Windows)
  fdt             fdt device tree
  kvm             KVM acceleration support
  hax             HAX acceleration support
  hvf             Hypervisor.framework acceleration support
  whpx            Windows Hypervisor Platform acceleration support
  rdma            Enable RDMA-based migration
  pvrdma          Enable PVRDMA support
  vde             support for vde network
  netmap          support for netmap network
  linux-aio       Linux AIO support
  linux-io-uring  Linux io_uring support
  cap-ng          libcap-ng support
  attr            attr and xattr support
  vhost-net       vhost-net kernel acceleration support
  vhost-vsock     virtio sockets device support
  vhost-scsi      vhost-scsi kernel target support
  vhost-crypto    vhost-user-crypto backend support
  vhost-kernel    vhost kernel backend support
  vhost-user      vhost-user backend support
  vhost-vdpa      vhost-vdpa kernel backend support
  spice           spice
  rbd             rados block device (rbd)
  libiscsi        iscsi support
  libnfs          nfs support
  smartcard       smartcard support (libcacard)
  libusb          libusb (for usb passthrough)
  live-block-migration   Block migration in the main migration stream
  usb-redir       usb network redirection support
  lzo             support of lzo compression library
  snappy          support of snappy compression library
  bzip2           support of bzip2 compression library
                  (for reading bzip2-compressed dmg images)
  lzfse           support of lzfse compression library
                  (for reading lzfse-compressed dmg images)
  zstd            support for zstd compression library
                  (for migration compression and qcow2 cluster compression)
  seccomp         seccomp support
  coroutine-pool  coroutine freelist (better performance)
  glusterfs       GlusterFS backend
  tpm             TPM support
  libssh          ssh block device support
  numa            libnuma support
  libxml2         for Parallels image format
  tcmalloc        tcmalloc support
  jemalloc        jemalloc support
  avx2            AVX2 optimization support
  avx512f         AVX512F optimization support
  replication     replication support
  opengl          opengl support
  virglrenderer   virgl rendering support
  xfsctl          xfsctl support
  qom-cast-debug  cast debugging support
  tools           build qemu-io, qemu-nbd and qemu-img tools
  bochs           bochs image format support
  cloop           cloop image format support
  dmg             dmg image format support
  qcow1           qcow v1 image format support
  vdi             vdi image format support
  vvfat           vvfat image format support
  qed             qed image format support
  parallels       parallels image format support
  sheepdog        sheepdog block driver support
  crypto-afalg    Linux AF_ALG crypto backend driver
  capstone        capstone disassembler support
  debug-mutex     mutex debugging support
  libpmem         libpmem support
  xkbcommon       xkbcommon support
  rng-none        dummy RNG, avoid using /dev/(u)random and getrandom()
  libdaxctl       libdaxctl support</pre>
<div class="blog_h2"><span class="graybg">第一台虚拟机</span></div>
<p>要创建虚拟机，首先要创建一个虚拟磁盘，然后从光驱启动此虚拟机：</p>
<pre class="crayon-plain-tag">mkdir -p ~/Vmware/KVM

# 以qcow2格式创建一个16G的虚拟磁盘，注意，默认不会预先分配空间
qemu-img create -f qcow2 ~/Vmware/KVM/centos7-base.img 16G

# 指定光盘镜像，从光驱启动虚拟机
# -hda 第一块硬盘的镜像
# -cdrom 光驱的镜像，你可以把宿主的/dev/cdrom传入，这样可以使用物理光驱
# -boot 指定启动顺序，d表示第一个光驱，c表示第一块硬盘
# -m 为虚拟机分配多少内存，默认单位M，默认128M
qemu-system-x86_64 -enable-kvm -hda ~/Vmware/KVM/centos7-base.img  -boot d -m 512
                   -cdrom ~/Software/OS/CentOS-7-x86_64-Minimal-1503-01.iso</pre>
<p>上述命令执行完毕之后，会弹出一个窗口，该窗口相当于虚拟机的显示器。你可以在其中完成操作系统的安装。安装完毕后，执行下面的命令，即可启动虚拟机：</p>
<pre class="crayon-plain-tag">qemu-system-x86_64 -enable-kvm -hda ~/Vmware/KVM/centos7-base.img -m 512</pre>
<p>后续几个章节，我们深入学习客户机硬件的定制，以满足不同应用场景的需要、提高客户机的性能。</p>
<div class="blog_h1"><span class="graybg">配置CPU</span></div>
<p>使用选项<pre class="crayon-plain-tag">-cpu</pre> 选项可以选择客户机使用的CPU，执行<pre class="crayon-plain-tag">qemu-system-x86_64 -cpu help</pre> 可以列出QEMU支持的CPU名称、可用的CPUID标记。</p>
<p>你可以这样配置一个CPU：</p>
<pre class="crayon-plain-tag">-cpu SandyBridge,+erms,+smep,+fsgsbase,+pdpe1gb,+rdrand,+f16c,+osxsave,+dca,+pcid,+pdcm,\
     +xtpr,+tm2,+est,+smx,+vmx,+ds_cpl,+monitor,+dtes64,+pbe,+tm,+ht,+ss,+acpi,+ds,+vme</pre>
<p>+表示启用CPU特性，如果要禁用CPU特性，可以使用<pre class="crayon-plain-tag">-</pre> 。</p>
<div class="blog_h2"><span class="graybg">SMP配置</span></div>
<p>所谓对称多处理（Symmetrical Multi-Processing） ，是指在一个计算机上汇集了一组处理器，各处理器<span style="background-color: #c0c0c0;">共享内存子系统以及总线结构</span>。在PC机上QEMU最多可以模拟255个CPU。</p>
<p>你可以这样配置SMP：<pre class="crayon-plain-tag">-smp 1,sockets=1,cores=1,threads=1</pre> 。这个配置表示主板上有一个CPU插槽、1个CPU、每个CPU具有1核心、每个核心具有1个硬件线程（超线程）。</p>
<div class="blog_h1"><span class="graybg">配置磁盘</span></div>
<p>你可以在宿主机上创建一个磁盘镜像文件，然后供客户机使用。客户机磁盘I/O都将针对此文件。镜像文件可以有几种格式。</p>
<div class="blog_h2"><span class="graybg">Raw镜像</span></div>
<p>这种镜像的特点是<span style="background-color: #c0c0c0;">格式简单，性能较好</span>。</p>
<p>你的文件系统（例如Ext3）必须支持稀疏文件（sparse file），才能避免不必要的磁盘空间占用。稀疏文件是一种高效使用磁盘空间的技术，当文件大小很大，而其绝大部分块都是空白（未使用）的时，可以基于文件元数据来表示那些空白的块（而不是真实的硬盘空间）。</p>
<p>创建Raw镜像：</p>
<pre class="crayon-plain-tag">qemu-img create -f raw hda.img 1G

# 查看镜像信息
qemu-img info hda.img 
# image: hda.img
# file format: raw
# virtual size: 1.0G (1073741824 bytes)
# disk size: 0</pre>
<p>你也可以使用dd命令产生Raw镜像，例如：</p>
<pre class="crayon-plain-tag"># 产生非稀疏文件：块大小1MB，写入1024个块，虚拟大小1G，实际大小1G
dd if=/dev/zero of=hda.img bs=1024k count=1024
# 产生稀疏文件：块大小1MB，写入0个块，虚拟大小1G，实际大小0
dd if=/dev/zero of=hda.img bs=1024k count=0 seek=1024</pre>
<div class="blog_h2"><span class="graybg">qcow2镜像</span></div>
<p>qcow2镜像的动态增长的，即使文件系统不支持稀疏文件，它也会尽可能的小。qcow2支持Copy-on-write、镜像、压缩、加密。 </p>
<p>正是由于qcow2支持Copy-on-write，我们才可以使用<span style="background-color: #c0c0c0;">backing file</span>——用一个镜像保存针对另外一个镜像的改变，而后面那个镜像不需要被改动。这是<span style="background-color: #c0c0c0;">多虚拟机公用一个Base镜像，以及Snapshot的基础</span>。</p>
<div class="blog_h3"><span class="graybg">qcow2原理</span></div>
<p>qcow2镜像文件的结构如下图所示：</p>
<p><img class="aligncenter size-full wp-image-13261" src="https://blog.gmem.cc/wp-content/uploads/2015/08/qcow2fmt.png" alt="qcow2fmt" width="640" height="178" /></p>
<p>qcow2镜像文件由一个头、几张表、数据簇组成。所有数据都存放在数据簇（Data Clusters）中，每个数据簇是512字节的扇区。为了方便管理这些数据簇，qcow2建立了两级表：L1、L2。其中L1表的条目指向L2表，而L2表的条目指向数据簇。</p>
<p>要定位数据，需要3个偏移量构成的数组：</p>
<ol>
<li>通过位于Header中的L1表指针  +offset[0]，得到L2表的指针</li>
<li>L2表指针 + offset[1]，得到数据簇指针</li>
<li>数据簇指针 + offset[2]，得到目标数据的指针</li>
</ol>
<div class="blog_h3"><span class="graybg">创建qcow2镜像</span></div>
<p>你可以这样创建一个qcow2镜像：</p>
<pre class="crayon-plain-tag">qemu-img create -f qcow2 hda-back.img 16G</pre>
<div class="blog_h3"><span class="graybg">转为backing file</span></div>
<p>然后，在未来某个时刻把它作为backing file使用：</p>
<pre class="crayon-plain-tag">qemu-img create -f qcow2 -o backing_file=hda-back.img hda.img</pre>
<div class="blog_h3"><span class="graybg">压缩、加密和扩展</span></div>
<p>镜像hda.img在一开始是空白的，所有数据都是从hda-back.img中获取，一旦发生写入操作，hda.img就开始有数据而hda-base.img保持不变。</p>
<p>使用下面的命令可以压缩一个qcow2镜像：</p>
<pre class="crayon-plain-tag">qemu-img convert -c -f qcow2 -O qcow2 hda.img hda.compressed.img</pre>
<p>使用下面的命令可以为一个qcow2镜像设加密：</p>
<pre class="crayon-plain-tag">qemu-img convert -o encryption -f qcow2 -O qcow2  hda.img hda.encrypted.img
# 提示输入密码</pre>
<p>使用压缩镜像启动虚拟机时，必须在Monitor中输入密码才可以。</p>
<p>使用下面的命令，可以扩展一个qcow2镜像的大小：</p>
<pre class="crayon-plain-tag">qemu-img resize hda.img +10G</pre>
<p>注意：扩大得到的空间，不会被分区或者格式化。 </p>
<div class="blog_h3"><span class="graybg">清理空白</span></div>
<p>要移除镜像中的spare space，直接qcow2-to-qcow2转换即可，压缩（-c）可选：</p>
<pre class="crayon-plain-tag">qemu-img convert -O qcow2 source.qcow2 shrunk.qcow2</pre>
<div class="blog_h3"><span class="graybg">变基rebase</span></div>
<p>rebase操作用于改变一个镜像的backing镜像：</p>
<pre class="crayon-plain-tag"># -u 表示unsafe模式，在此模式下，仅仅改变backing文件的路径，不对文件内容进行检查
#    用于backing文件移动的情况
# -p 表示safe模式，在此模式下，执行真正的rebase操作。backing文件的内容可能和之前
#    不同，qemu-img会小心处理，确保VM可见的内容不变。为达成这一点，新旧backing
#    文件的差异，会合并到被改变镜像中
                    # 格式
qemu-img rebase -u  -f qcow2   
  # 新的backing文件位置                                      新backing文件格式
  -b /home/alex/Vmware/libvirt/images/sdd/xenial-base.qcow2 -F qcow2 
  # 被处理镜像文件
  /home/alex/Vmware/libvirt/images/sdd/xenial-100.qcow2</pre>
<div class="blog_h2"><span class="graybg">格式转换</span></div>
<p>你可以把一个镜像的格式在Raw和qcow2之间进行转换：</p>
<pre class="crayon-plain-tag"># 把Raw格式的hda.img转换为qcow2格式的hda.qcow2
qemu-img convert -f raw -O qcow2 hda.img hda.qcow2</pre>
<div class="blog_h2"><span class="graybg">使用快照</span></div>
<p>快照（Snapshot）是Copy-on-write的一种应用。QEMU支持两种快照：</p>
<ol>
<li>内部快照（internal snapshot）：在qcow2镜像的snapshot table中维护的快照，所有快照都存放在一个镜像文件中</li>
<li>外部快照（external snapshot）：与Backing file很类似，在外部文件中创建新的镜像，原先的镜像只读</li>
</ol>
<div class="blog_h3"><span class="graybg">内部快照</span></div>
<p>内部快照的原理是：</p>
<ol>
<li>创建一个Snapshot后，在Snapshot Table中新增一项，复制L1 Table</li>
<li>当L2 Table或者Data Cluster发生改变，则把改变前的数据复制一份（Copy-on-write），由新创建的Snapshot的L1 Table来管理</li>
<li>L2 Table或者Data Cluster的变化，直接写到原始位置</li>
<li>要删除快照，很简单，直接把Snapshot Table对应项、以及复制的L1-L2-DS删除即可</li>
<li>要加载快照，则需要依据L1-L2-DS信息，将其合并到镜像的L1-L2-DS信息中</li>
</ol>
<p>可以使用Monitor来创建、加载、删除内部快照：</p>
<pre class="crayon-plain-tag"># 保存一个内部快照
(qemu) savevm snapshot-1

qemu-img info hda.img
# 输出如下：
#Snapshot list:
#ID        TAG                 VM SIZE                DATE       VM CLOCK
#1         snapshot-1             112M 2016-09-07 18:05:48   00:00:21.536
#Format specific information:
#    compat: 1.1
#    lazy refcounts: false

# 加载内部快照
(qemu) loadvm snapshot-1

# 删除内部快照
(qemu) delvm snapshot-1</pre>
<div class="blog_h3"><span class="graybg">外部快照</span></div>
<p>外部快照与内部快照相反：内部快照是原数据变化，外部快照则是新文件变化。</p>
<p>可以使用Monitor来管理外部快照：</p>
<pre class="crayon-plain-tag">snapshot_blkdev ide0-hd0 snapshot.img qcow2</pre>
<div class="blog_h2"><span class="graybg">配置客户机磁盘</span></div>
<p>有了磁盘镜像文件后，你需要为qemu-system-*指定参数，给客户机增加磁盘。有几种不同的配置方式：</p>
<pre class="crayon-plain-tag"># 最简单的方式
-hda hda.img 

# 使用-drive配置块设备，可以指定if为virtio来提升性能
-drive file=hda.img,index=0,media=disk,if=virtio

# 使用-device配置通用设备
-drive file=hda.img,if=none,id=virtio-disk0,format=qcow2,cache=none 
# 可以指定virtio-blk-pci来提升性能
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x4,drive=virtio-disk0,bootindex=1</pre>
<div class="blog_h1"><span class="graybg">配置网络</span></div>
<p>QEMU中的网络，包含两部分的内容：</p>
<ol>
<li>客户机使用的虚拟网络设备</li>
<li>和上述虚拟设备通信的<span style="background-color: #c0c0c0;">网络后端</span>，这些后端负责把虚拟设备的数据包发到宿主机的网络中</li>
</ol>
<p>要创建一个网络后端，可以指定如下选项：</p>
<pre class="crayon-plain-tag"># TYPE为后端类型：user、tap、bridge、socket、vde等
# id为一个标识符，将虚拟网络设备和网络后端关联在一起
# 如果客户机有多个虚拟网络设备，则每一个都需要自己的网络后端
-netdev TYPE,id=NAME,...</pre>
<p>QEME支持多种网络后端。</p>
<div class="blog_h2"><span class="graybg">USER后端</span></div>
<p>如果没有指定网络选项，QEMU默认会模拟单张Intel e1000 PCI网卡，该网卡基于user后端（SLIRP）连接到宿主机：</p>
<pre class="crayon-plain-tag"># 不指定网络
qemu 
# 等价配置。自0.12开始废弃的配置方式 -net nic相当于-device DEVNAME；-net TYPE相当于-netdev TYPE
qemu -hda disk.img -net nic -net user
# 等价配置。-netdev指定网络后端，-device指定虚拟网络设备，后者通过netdev字段引用后端的ID
qemu -netdev user,id=network0 -device e1000,netdev=network0</pre>
<p>在客户机看来：</p>
<ol>
<li>本身的IP地址被分配为 10.0.2.15+</li>
<li>分配IP的虚拟DHCP为 10.0.2.2</li>
<li>虚拟DNS服务器为 10.0.2.3</li>
<li>虚拟Samba服务器为 10.0.2.4，客户机可以通过此服务器访问宿主机的文件系统</li>
</ol>
<p>用户模式网络可以很方便的访问网络资源。但是它有很多限制：</p>
<ol>
<li>默认的，它运作方式类似于防火墙，且不允许任何入站流量。这个限制可以通过端口重定向解决</li>
<li>仅仅支持TCP、UDP协议，对于ICMP则不支持</li>
<li>性能比较差</li>
</ol>
<p>为了支持入站请求，你可以使用端口重定向（Redirecting ports）——把针对宿主机某个端口的请求转发给客户机的某个端口。映射后，客户机可以对外提供SSH、HTTP等服务：</p>
<pre class="crayon-plain-tag"># 把宿主机的7080端口重定向到客户机的80端口；把宿主机的7022端口重定向到客户机的22端口
qemu-system-x86_64 -redir tcp:7080::80 -redir tcp:7022::22 -hda ~/Vmware/KVM/centos7-base.img -m 512 

# 从宿主机SSH到客户机
ssh root@127.0.0.1 -p 7022</pre>
<p>你可以不使用默认的10.0.2网段：</p>
<pre class="crayon-plain-tag">-netdev user,id=network0,net=192.168.5.0/24,dhcpstart=192.168.5.9 </pre>
<div class="blog_h3"><span class="graybg">客户机OS配置</span></div>
<p>依据客户机安装的操作系统，可能需要进行一些配置，才能正常使用网络。以CentOS 7 Minimal + 用户模式网络为例，需要修改以下配置文件：</p>
<pre class="crayon-plain-tag">NETWORKING=yes
# 如果不使用IPV6
NETWORKING_IPV6=no</pre><br />
<pre class="crayon-plain-tag"># 如果不使用IPV6
IPV6INIT=no
# 开机启动此网卡，默认不启动
ONBOOT=yes</pre>
<p>网关、DNS不需要设置。修改完这些配置文件后，重启客户机网络：<pre class="crayon-plain-tag">/etc/init.d/network restart</pre>  。然后执行<pre class="crayon-plain-tag">yum update</pre> 测试一下能否正常联网（不要使用ping测试）</p>
<div class="blog_h2"><span class="graybg">TAP后端</span></div>
<p>QEMU的TAP后端利用宿主机的TAP设备，为客户机提供完整的桥接网络支持，如果外部需要使用标准端口连接到客户机， 或者多个客户机需要相互通信，可以使用该方式。 TAP后端还具有以下优势：</p>
<ol>
<li>非常好的性能</li>
<li>可以配置以支持各种网络拓扑</li>
</ol>
<p>但是，你需要在宿主机上进行网络拓扑的配置，而且各种系统的配置不同。</p>
<p>使用TAP后端前，你需要确认你的宿主机的内核支持TAP网络接口：<pre class="crayon-plain-tag">/dev/net/tun</pre> 文件存在则说明支持。如果没有这样的文件，可以尝试手工创建：</p>
<pre class="crayon-plain-tag">sudo mkdir /dev/net
sudo mknod /dev/net/tun c 10 200
sudo /sbin/modprobe tun</pre>
<div class="blog_h3"><span class="graybg">基于TAP的私有桥接网络</span></div>
<p>如果你想创建几个客户机之间的私有网络，可以使用该方式。未参与进来的客户机、真实网络无法看到此网络。</p>
<p>如果你不是root，则你需要<pre class="crayon-plain-tag">/dev/kvm</pre> 的读写权限。</p>
<p>首先，添加一个以太网桥设备：</p>
<pre class="crayon-plain-tag">sudo ip link add br0 type bridge
# 也可以使用：sudo brctl addbr br0添加网桥
# 要删除网桥，执行： ip link delete br0
# 注意：网桥会在重启后消失

# 启用此网桥
sudo ip link set br0 up

# 为网桥分配IP地址
sudo ip addr add 10.0.0.1 dev br0

# 在宿主机添加一条直接路由，便于它能和客户机通信
sudo ip route add 10.0.0.0/8 dev br0</pre>
<p>创建一个创建TAP设备并桥接到网桥的脚本：</p>
<pre class="crayon-plain-tag">#!/bin/sh

switch=br0

if [ -n "$1" ];then
        # tunctl -u `whoami` -t $1
        # 添加一个tap设备，在我的机器上不需要，原因见下面
        # ip tuntap add $1 mode tap user `whoami`
        # 不知道从什么时候开始，QEMU会在执行此脚本之前就创建好tap设备，因此会报下面的错误
        # ioctl(TUNSETIFF): Device or resource busy
        # 启动tap设备
        ip link set $1 up
        # brctl addif $switch $1
        # 将网桥和tap设备进行桥接
        ip link set $1 master $switch
        exit 0
else
        echo "Error: no interface specified"
        exit 1
fi</pre>
<p>创建一个生成随机MAC地址的脚本：</p>
<pre class="crayon-plain-tag">#!/bin/bash
# generate a random mac address for the qemu nic
printf 'DE:AD:BE:EF:%02X:%02X\n' $((RANDOM%256)) $((RANDOM%256))</pre>
<p>启动客户机的脚本：</p>
<pre class="crayon-plain-tag">#!/bin/bash
# $1 base name of virtual disk
# $2 memory size
# $3 tap device id

mac=`/usr/bin/qemu-genmac`
src=/usr/bin/qemu-ifup
sudo qemu-system-x86_64 -enable-kvm -device e1000,netdev=$3,mac=$mac -netdev tap,id=$3,script=$src,downscript=no \
                        -hda ~/Vmware/KVM/$1.img -m $2</pre>
<p>为上面的脚本文件添加可执行权限：</p>
<pre class="crayon-plain-tag">sudo chmod +x /usr/bin/qemu-ifup-br0
sudo chmod +x /usr/bin/qemu-genmac
sudo chmod +x /usr/bin/qemu-start-br0</pre>
<p>执行下面的命令，启动一台客户机（或者更多虚拟机，但是命令中的tap0要更换为不同的名字）：</p>
<pre class="crayon-plain-tag">/usr/bin/qemu-start centos7-base 512 tap0</pre>
<p>修改客户机的IP地址，使用10.0.0.0/8网段：</p>
<pre class="crayon-plain-tag">TYPE=Ethernet
BOOTPROTO=static
DEFROUTE=yes
PEERDNS=yes
PEERROUTES=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=no
NAME=ens3
UUID=d9f47102-b177-4a27-ae98-86f6939d6680
DEVICE=ens3
ONBOOT=yes
IPADDR=10.0.0.10
PREFIX=8
GATEWAY=10.0.0.1</pre>
<p>好了，你现在可以互相ping客户机和宿主机，应该可以正常连通了。 </p>
<div class="blog_h3"><span class="graybg">私有桥接下访问互联网</span></div>
<p>上一节介绍的这种基于TAP的私有桥接网络，可以让客户机、宿主机相互连通，但是客户机无法访问互联网。</p>
<p>要解决此问题，你可以选择以下方法之一：</p>
<ol>
<li>让客户机通过宿主机暴露的HTTP/SOCKS代理上网</li>
<li>配置宿主机的路由规则，设置好源地址转换即可：<br />
<pre class="crayon-plain-tag"># 宿主机需要启用IP转发功能，这样它就可以像路由器那样中转IP封包了
sudo sysctl -w net.ipv4.ip_forward=1 
# 对客户机网段进行源地址转换
sudo iptables -t nat -A POSTROUTING  -s 10.0.0.0/255.0.0.0 ! -d 10.0.0.0/255.0.0.0 -j MASQUERADE </pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">公共桥接网络</span></div>
<p>此方式和私有桥接网络类似，主要区别是，除了TAP设备桥接到网桥之外，以太网卡（例如eth0）也桥接到网桥（例如br1）。</p>
<p>你可以通过发行版的配置文件来配置网桥：</p>
<pre class="crayon-plain-tag"># 注意网络管理器组件的影响
# 去掉 auto eth0，改为：
auto br1

# 配置br1
iface br1 inet dhcp
    bridge_ports    eth0
    bridge_stp      off
    bridge_maxwait  0
    bridge_fd       0
    # 这里附加上原来属于eth0的配置</pre>
<p>或者基于脚本来配置： </p>
<pre class="crayon-plain-tag">sudo ip link add br1 type bridge
sudo ip link set br1 up
sudo ip link set eth0 master br1 

# DHCP
sudo killall dhclient &amp;&amp; sudo ip addr flush dev eth0
sudo dhclient br1</pre>
<p>无论用哪种方式，都应该注意到eth0的IP地址需要<span style="background-color: #c0c0c0;">转移给br1</span>，这样才能确保网络正常运作——br1必须在链路层接收到相关ARP请求，并决定是否需要转发给客户机，eth0没有这种转发能力。</p>
<p>如果eth0所在网络是基于DHCP的，那么客户机配置为DHCP后，会自动获取公共IP地址。否则，需要手工设置客户机的IP地址。 </p>
<div class="blog_h3"><span class="graybg">基于TAP的桥接的简化配置</span></div>
<p>现在QEMU支持自动桥接TAP设备到宿主机的一个网桥，因此你不再需要编写脚本，修改网络后端为bridge即可：</p>
<pre class="crayon-plain-tag">-netdev bridge,id=tap0,br=br0</pre>
<p>注意，使用上述选项时，QEMU需要读取配置文件/etc/qemu/bridge.conf，你只需在此文件中添加一行代码：<pre class="crayon-plain-tag">allow br0</pre> </p>
<p>你可以编写如下脚本自动创建网桥、配置iptables规则。示例：</p>
<pre class="crayon-plain-tag"># Create private bridge link for QEMU
/sbin/ip link add br0 type bridge
/sbin/ip link set br0 up
/sbin/ip addr add 10.0.0.1 dev br0
/sbin/ip route add 10.0.0.0/8 dev br0
# NAT for 10.0.0.0/8
/sbin/iptables -t nat -A POSTROUTING  -s 10.0.0.0/255.0.0.0 ! -d 10.0.0.0/255.0.0.0  -j MASQUERADE


# Create public bridge link for QEMU
/sbin/ip link add br1 type bridge
/sbin/ip link set br1 up
/sbin/ip link set eth0 master br1 
/usr/bin/killall dhclient &amp;&amp; /sbin/ip addr flush dev eth0
/sbin/dhclient br1</pre>
<div class="blog_h3"><span class="graybg">macvtap直连</span></div>
<p>建议和libvirt一起使用macvtap。 </p>
<div class="blog_h3"><span class="graybg">基于libvirt的桥接</span></div>
<p>在使用libvirt时，客户机（Domain）的网络接口配置可以简化为：</p>
<pre class="crayon-plain-tag">&lt;interface type='bridge'&gt;
    &lt;mac address='DE:AD:BE:EF:F1:00'/&gt;
    &lt;source bridge='br0'/&gt;
    &lt;target dev='tap0'/&gt;
    &lt;model type='virtio'/&gt;
&lt;/interface&gt;</pre>
<div class="blog_h3"><span class="graybg">基于libvirt虚拟局域网的桥接</span></div>
<p>可以使用libvrit的虚拟局域网，这样宿主机上<span style="background-color: #c0c0c0;">不会为客户机创建专门的tap设备，那些手工编写的脚本也全都不需要了</span>。虚拟网络配置示例：</p>
<pre class="crayon-plain-tag">&lt;network&gt;
  &lt;name&gt;default&lt;/name&gt;
  &lt;uuid&gt;9bae4de8-ca58-48c5-ba58-109aebf8b954&lt;/uuid&gt;
  &lt;forward mode='nat'&gt;
  &lt;/forward&gt;
  &lt;bridge name='virbr0' stp='on' delay='0'/&gt;
  &lt;ip address='10.0.0.1' netmask='255.0.0.0'&gt;
    &lt;dhcp&gt;
      &lt;range start='10.0.0.100' end='10.0.0.200'/&gt;
    &lt;/dhcp&gt;
  &lt;/ip&gt;
&lt;/network&gt;</pre>
<p>客户机（Domain）的网络接口配置示例：</p>
<pre class="crayon-plain-tag">&lt;interface type='network'&gt;
    &lt;mac address='DE:AD:BE:EF:F1:00'/&gt;
    &lt;source network='default'/&gt;
    &lt;model type='virtio'/&gt;
&lt;/interface&gt;</pre>
<p>另外，libvirt的虚拟网络提供了DHCP功能，因此客户机的IP地址不需要静态设置。</p>
<div class="blog_h1"><span class="graybg">其它配置</span></div>
<div class="blog_h2"><span class="graybg">SMBIOS</span></div>
<p>SMBIOS即DMI表，存放了X86系统硬件信息，这个表依据DMI type分为数十个段，type0是BIOS、type1是系统信息、type2是主板信息……</p>
<p>QEMU支持模拟这些信息，例如：</p>
<pre class="crayon-plain-tag"># 设置客户机的type1信息
-smbios type=1,manufacturer=OpenStack Foundation,product=OpenStack Nova,version=2011,serial=8059dfb4,uuid=1f8ee7f308</pre>
<div class="blog_h2"><span class="graybg">内存</span></div>
<p>要设置客户机的内存容量，可以使用<pre class="crayon-plain-tag">-m</pre> ，默认单位MB。</p>
<p>客户机没必要占据着空闲的内存不用，因此我们一般启用内存实际大小的动态调整功能，例如：</p>
<pre class="crayon-plain-tag">-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x5</pre>
<div class="blog_h1"><span class="graybg">半虚拟化和virtio</span></div>
<p>在本文的第一章，谈到周围硬件性能的时候，我们提及了virtio——它是规定了<span style="background-color: #c0c0c0;">虚拟设备的前端驱动与宿主机硬件的后端驱动之间通信接口的标准</span>，并且知道目前的很多Linux发行版已经把virtio驱动编译进内核了。前面的章节我们也使用了很多virtio驱动，包括磁盘、网络、内存相关的。</p>
<p><span style="background-color: #c0c0c0;">基于virtio驱动的虚拟设备，我们成为“半虚拟化设备”</span>，因为这些设备驱动知道自己工作在虚拟化模式下。为客户机配置半虚拟化设备，可以提高内存、硬盘、网络方面的性能，由其对于网络，性能提升很明显。</p>
<p>除了virtio，Vmware Tools也属于半虚拟化驱动，QEMU客户机也可以利用Vmware Tools（例如-vga指定vmware）。virtio驱动的具体实现包括：virtio-blk、virtio-net、virtio-pci、virtio-balloon、virtio-console等。</p>
<p>半/全虚拟化的区别如下：</p>
<ol>
<li>在全虚拟化状态下，Guest OS不知道自己是虚拟机，于是像发送普通的IO一样发送数据，<span style="background-color: #c0c0c0;">被Hypervisor拦截</span>，转发给真正的硬件。</li>
<li>在半虚拟化状态下，Guest OS知道自己是虚拟机（需安装半虚拟化驱动），所以数据直接发送给半虚拟化设备，经过特殊处理，发送给真正的硬件</li>
</ol>
<div class="blog_h2"><span class="graybg">virtio-balloon</span></div>
<p>这是一个特殊的半虚拟化设备，它能够动态（不需要重启客户机）的调整客户机的内存大小。如果你指定了-m参数，则不能调整的比-m更大。使用选项<pre class="crayon-plain-tag">-balloon virtio</pre> 可以添加Ballooning设备。</p>
<div class="blog_h2"><span class="graybg">virtio-blk</span></div>
<p>要基于半虚拟化来访问磁盘，可以使用选项：<pre class="crayon-plain-tag">-drive file=vda.qcow2,if=virtio</pre> ，使用virto_blk驱动的硬盘，在客户机里对应的设备文件是/dev/vda（而IDE硬盘是/dev/hda、基于SATA的硬盘则显示为/dev/sda） 。</p>
<p>可以使用驱动<span style="background-color: #c0c0c0;">virtio-blk-data-plane进一步提高性能</span>（I/O性能较virtio-blk能提高10-20%），此驱动自QEMU 1.4开始引入。与传统的virtio-blk不同的是，virtio-blk-data-plane为每个块设备独立分配一个线程用于I/O处理，此线程不需要和QEMU执行线程同步、竞争锁。此驱动基于宿主机的原生AIO响应客户机的请求。启用此驱动的选项示例：</p>
<pre class="crayon-plain-tag">-drive if=none,id=drive0,cache=none,aio=native,format=raw,file=vda.img
-device virtio-blk-pci,drive=drive0,scsi=off,x-data-plane=on</pre>
<p>但是，启用virtio-blk-data-plane后，存储迁移（storage migration）、热拔插、I/O限流（throttling） 等功能无法使用。而且该驱动仅支持Raw格式的磁盘。</p>
<div class="blog_h2"><span class="graybg">virtio-net</span></div>
<p>要基于半虚拟化来访问网络，可以使用选项：<pre class="crayon-plain-tag">-device virtio-net-pci,netdev=network0</pre> 。你应当总是考虑启用<span style="background-color: #c0c0c0;">半虚拟化网卡，因为性能会有很大的提升</span>。</p>
<p>宿主机网卡的某些特性可能会影响virtio的性能，例如：</p>
<ol>
<li>TSO（TCP Segmentation Offload）：通过网络设备进行TCP段的分割，从而来提高网络性能</li>
<li>GSO（Generic Segmentation Offload）：类似，用TCPv6、UDP等传输层协议</li>
</ol>
<p>你可以开关这些特性来测试对客户机网络性能的影响。要检查宿主机网卡是否支持、开启这些特性，可以执行命令：<pre class="crayon-plain-tag">ethtool -k eth0</pre> 。</p>
<div class="blog_h1"><span class="graybg">配置NBD</span></div>
<p>网络块设备（Network Block Device）是一种把虚拟块设备通过TCP/IP暴露出去，供远程共享访问的技术。</p>
<div class="blog_h2"><span class="graybg">暴露NBD</span></div>
<div class="blog_h3"><span class="graybg">通过套接字暴露</span></div>
<p>你可以通过UNIX套接字来暴露：</p>
<pre class="crayon-plain-tag">qemu-nbd -t -k /home/alex/Vmware/KVM/.images/fedora-108 fedora-108/hda.img</pre>
<p> 也可以通过普通套接字来暴露：</p>
<pre class="crayon-plain-tag">qemu-nbd  -p 1025 fedora-108/hda.img </pre>
<div class="blog_h3"><span class="graybg">挂载到NBD设备</span></div>
<p>甚至是把镜像直接挂载到宿主机的NBD设备中：</p>
<pre class="crayon-plain-tag"># 在宿主机上启用NBD内核模块，最多16个分区：
sudo modprobe nbd max_part=16
# 查看NBD设备文件
ls /dev/nbd*
# 输出/dev/nbd0 ... /dev/nbd15

# 挂载
sudo qemu-nbd -c /dev/nbd0 fedora-108/hda.img

# 查看nbd0的分区情况
sudo fdisk -l /dev/nbd0
#  Device Boot Start End Blocks Id System
#  /dev/nbd0p1 * 2048 1026047 512000 83 Linux
#  /dev/nbd0p2 1026048 33554431 16264192 8e Linux LVM</pre>
<div class="blog_h2"><span class="graybg">使用NBD</span></div>
<p>客户机可以直接使用NBD作为磁盘：</p>
<pre class="crayon-plain-tag"># 使用UNIX套接字：
qemu-system-x86_64 -hda nbd:unix:/home/alex/Vmware/KVM/.images/fedora-108
# 使用普通套接字
qemu-system-x86_64 -hda nbd:10.0.0.1:1025</pre>
<div class="blog_h1"><span class="graybg">迁移</span></div>
<p>QEMU支持<span style="background-color: #c0c0c0;">离线或者在线</span>的迁移，你可以在Monitor中使用迁移命令。当迁移完毕后，虚拟机会在目标主机上继续运行。</p>
<p>AMD和Intel宿主机之间可以随意的迁移虚拟机，64位虚拟机只能迁移到64位宿主机上，32位则没有限制。某些老旧的Intel CPU不支持NX（禁止执行比特位），这种CPU处于启用NX的宿主机群中，会导致问题，你需要禁止客户机的NX：<pre class="crayon-plain-tag">-cpu qemu64,-nx</pre> 。</p>
<p>QEMU的迁移功能具有以下特性：</p>
<ol>
<li>极短暂的客户机停机时间</li>
<li>如果迁移成功，则客户机在目标主机上运行；如果迁移失败，则客户机继续在源主机上运行</li>
<li>几乎对硬件没有依赖</li>
</ol>
<div class="blog_h2"><span class="graybg">使用共享存储</span></div>
<p>使用共享存储时，QEMU迁移会很便利，因为不牵涉到磁盘映像的移动。共享存储包括：NFS、NBD、SAN等。 我们以NBD为例说明：</p>
<ol>
<li>启动供源、目的虚拟机共享的NBD服务：<pre class="crayon-plain-tag">qemu-nbd -p 1025 --share=2 fedora-108/hda.img</pre> </li>
<li>确保源、目的虚拟机的配置，它们要具有相同的网络环境</li>
<li>启动源虚拟机：<br />
<pre class="crayon-plain-tag">sudo qemu-system-x86_64 -netdev bridge,id=tap0,br=br0 -device virtio-net-pci,netdev=tap0,mac=DE:AD:BE:EF:F1:08 
                        -hda nbd:10.0.0.1:1025 -monitor stdio -enable-kvm  </pre>
</li>
<li>源虚拟机运作一段时间后，其宿主机的硬件需要维护，因此准备迁移。在另外一台宿主机上启动目的虚拟机，并监听migration端口：<br />
<pre class="crayon-plain-tag"># qemu选项同源虚拟机，附加：
-incoming tcp:0:4444</pre></p>
<p> 注意，这个监<span style="background-color: #c0c0c0;">听端口是开在宿主机上的</span>。实际上，以-incoming启动目的虚拟机后，虚拟机是<span style="background-color: #c0c0c0;">处于Stopped状态</span>的</p>
</li>
<li>登录到源虚拟机，确认它与目的虚拟机的宿主机之间的网络是畅通的</li>
<li>在源虚拟机的Monitor中，发起迁移命令：<pre class="crayon-plain-tag">(qemu) migrate -d tcp:10.0.0.1:4444</pre> </li>
<li>在迁移过程中，可以通过<pre class="crayon-plain-tag">info migrate</pre> 查看迁移状态，完毕后会显示Migration status: completed，并列出迁移消耗的时间、停机时间</li>
<li>迁移完成后，源虚拟机变为Stopped，而目的虚拟机开始运行，获得<span style="background-color: #c0c0c0;">源虚拟机的全部瞬时状态</span></li>
</ol>
<div class="blog_h2"><span class="graybg">不使用共享存储</span></div>
<p>这种情况下，源虚拟机的磁盘镜像需要拷贝到目标宿主机中。因而需要更长的时间、更多的网络带宽消耗。步骤如下：</p>
<ol>
<li>查看源虚拟机的磁盘镜像信息：<pre class="crayon-plain-tag">qemu-img info fedora-108/hda.img</pre> </li>
<li>在目的机器上创建与之大小（Virtual size）一致的空磁盘镜像：<pre class="crayon-plain-tag">qemu-img create -f qcow2 fedora-108-m/hda.img 16G</pre> </li>
<li>使用与共享存储一样的步骤进行迁移，只是源、目的虚拟机使用各自的磁盘镜像</li>
</ol>
<div class="blog_h1"><span class="graybg">监控与调试</span></div>
<p>前面的章节我们已经多次使用QEMU的监控功能，通过使用QEMU的HMI（Monitor）可以在(qemu)提示符下进行各种监控操作，包括查看虚拟机信息、动态添加设备、执行迁移等等。在《QEMU命令与快捷键》一章我们会详细的讲解HMI命令，本章主要介绍监控相关的QEMU配置</p>
<div class="blog_h2"><span class="graybg">基于HMI（Monitor）监控</span></div>
<p>你可以通过多种方式使用Monitor：</p>
<ol>
<li>默认的，可以在QEMU的虚拟机窗口中，按Ctrl + Alt + 2切换到Monitor</li>
<li>可以使用<pre class="crayon-plain-tag">-monitor stdio</pre> ，让Monitor重定向到启动虚拟机的Terminal</li>
<li>可以启动一个TCP监听<pre class="crayon-plain-tag">-monitor tcp::4444,server,nowait</pre> ，这样你可以<pre class="crayon-plain-tag">telnet hostip:4444</pre> 访问Monitor</li>
<li>可以通过字符设备：<pre class="crayon-plain-tag">-chardev stdio,id=x -monitor chardev=x</pre> 访问Monitor</li>
</ol>
<div class="blog_h2"><span class="graybg">基于QMP监控</span></div>
<p>非交互式监控时，QEMU监控协议（QEMU Monitor Protocol）是更好的选择，这是一个基于JSON格式的协议。要启用QMP，你可以：</p>
<ol>
<li>基于stdio：<pre class="crayon-plain-tag">-qmp stdio</pre> </li>
<li>基于TCP：<pre class="crayon-plain-tag">-qmp tcp:localhost:4444,server</pre> </li>
<li>基于UNIX Socket：<pre class="crayon-plain-tag">-qmp unix:./qmp-sock,server</pre> </li>
</ol>
<div class="blog_h1"><span class="graybg">最佳实践</span></div>
<p>以下列出一些应用基于QEMU/KVM的虚拟化方案时的最佳实践：</p>
<ol>
<li>使用半虚拟化驱动virtio
<ol>
<li>性能好：延迟低、吞吐量高</li>
<li>纯虚拟设备的劣势：需要高吞吐能力的设备在硬件方面会有特殊的实现，这些纯虚拟设备是没法利用的</li>
<li>网络、块设备、内存，都可以使用virtio</li>
<li>兼容性较差</li>
</ol>
</li>
<li>虚拟机最好直接使用块设备做存储
<ol>
<li>性能好、无需管理宿主机的文件系统、无需管理稀疏文件</li>
<li>I/O 缓存以4K为边界</li>
<li>如果没有条件使用块设备，只能使用镜像文件</li>
<li>宿主机最好使用ext3文件系统，ext4的barrier会影响性能</li>
<li>Raw格式镜像的性能优于qcow2</li>
<li>选择正确的缓存策略，缓存模式推荐none，I/O调度器推荐Deadline I/O scheduler</li>
</ol>
</li>
<li>CPU配置
<ol>
<li>每个客户机相当于一个进程，而每个客户机的虚拟CPU相当于一个线程。因此超配CPU是可行的</li>
<li>CPU超配可能带来额外的上下文切换，影响性能</li>
<li>要保证客户机获得足够的时间片，可以利用cgroup的cpu.cfs_period_us、cpu.cfs_quota_us来干预CFS调度器的行为</li>
<li>Pin CPU：可以将虚拟CPU Pin到一个物理CPU，或者一组共享缓存的物理CPU，便于缓存共享。缺点是Pin导致其它空闲CPU可能得不到利用</li>
</ol>
</li>
<li>内存配置
<ol>
<li>使用内核特性KSM（Kernel Same Page Merging），KSM通过扫描将相同的内存区域设置为共享，并且Copy-on-write。共享内存节约可以内存空间，但是内存扫描同时影响性能</li>
<li>尽量避免使用swap，可以设置/proc/sys/vm/swappiness=0</li>
</ol>
</li>
<li>网络配置
<ol>
<li>使用tap类型的网络后端</li>
<li>启用PCI passthough可以提高性能，但是影响迁移</li>
</ol>
</li>
</ol>
<div class="blog_h1"><span class="graybg">QEMU命令与快捷键</span></div>
<div class="blog_h2"><span class="graybg">HMI</span></div>
<p>HMI即 Human Monitor Interface，是QEMU在运行客户机时提供的一个console（下面我们称此console为Monitor），它让你可以和运行中的虚拟机进行交互，你可以获得内存Dump、列出虚拟设备树、获取屏幕截图等操作。</p>
<div class="blog_h3"><span class="graybg">访问HMI</span></div>
<p>默认情况下QEMU使用SDL来显示客户机的视频输出，此所谓图形模式。如果启用-nographic选项则会禁用图形模式。</p>
<p>在图形模式下，你可以使用以下方式之一访问HMI：</p>
<ol>
<li>在客户机的虚拟控制台（客户机弹窗）访问HMI，按Ctrl + Alt + 2可以切换到Monitor，在其中你可以调用HMI命令</li>
<li>指定-monitor stdio，则启动虚拟机的Terminal变为Monitor</li>
</ol>
<p>在基于-nographic的非图形模式下，Monitor、虚拟串口都被重定向到stdio，你可以Ctrl + a c来切换。你可以同时把虚拟串口配置为<span style="background-color: #c0c0c0;">系统控制台</span>，这样你可以通过单个窗口完成客户机登录、HMI操作</p>
<div class="blog_h3"><span class="graybg">HMI命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>info</td>
<td>
<p>显示客户机的相关信息，示例：</p>
<p><pre class="crayon-plain-tag">info kvm           # 显示KVM支持情况
info pci           # 显示PCI信息
info qtree         # 显示QEMU系统总线树
info network       # 显示网络设备信息
info block         # 显示块设备信息
info blockstatus   # 显示块设备读写统计信息
info snapshots     # 显示快照信息
info migrate       # 显示迁移状态</pre>
</td>
</tr>
<tr>
<td>memsave</td>
<td>Dump客户机内存到宿主机的文件</td>
</tr>
<tr>
<td>screendump</td>
<td>屏幕截图</td>
</tr>
<tr>
<td>sendkey</td>
<td>键盘控制，示例：<pre class="crayon-plain-tag">sendkey ctrl-alt-f1</pre> </td>
</tr>
<tr>
<td>quit</td>
<td>退出客户机addr=0xM.0xN</td>
</tr>
<tr>
<td>system_powerdown</td>
<td>关闭虚拟机电源</td>
</tr>
<tr>
<td>system_reset</td>
<td>重启虚拟机</td>
</tr>
<tr>
<td>system_wakeup</td>
<td>唤醒休眠中的虚拟机</td>
</tr>
<tr>
<td>savevm</td>
<td>
<p>保存一个虚拟机快照，示例：<pre class="crayon-plain-tag">savevm blankos</pre> </p>
</td>
</tr>
<tr>
<td>loadvm</td>
<td>从快照加载虚拟机，示例：<pre class="crayon-plain-tag">loadvm blankos</pre> </td>
</tr>
<tr>
<td>delvm</td>
<td>删除一个虚拟机快照</td>
</tr>
<tr>
<td>snapshot_blkdev_internal</td>
<td>创建一个内部的块设备（主要指硬盘）快照，示例：<br />
<pre class="crayon-plain-tag">info block
# ide0-hd0: /home/alex/Vmware/KVM/fedora-108/hda.img (qcow2)
snapshot_blkdev_internal  ide0-hd0 blankos</pre>
</td>
</tr>
<tr>
<td>snapshot_delete_blkdev_internal</td>
<td>删除一个内部的块设备快照</td>
</tr>
<tr>
<td>snapshot_blkdev</td>
<td>创建一个外部的块设备快照，示例：<br />
<pre class="crayon-plain-tag">snapshot_blkdev ide0-hd0 blankos.img</pre></p>
<p>如果指定了文件参数，则此文件成为新的root镜像</p>
</td>
</tr>
<tr>
<td>migrate</td>
<td>执行虚拟机迁移</td>
</tr>
<tr>
<td>migrate_cancel</td>
<td>取消虚拟机迁移</td>
</tr>
<tr>
<td>migrate_set_speed</td>
<td>限制迁移带宽消耗</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">qemu-system-x86_64</span></div>
<p>该命令即QEMU模拟器，使用它可以指定硬件设备，并从虚拟磁盘镜像启动一台客户机。</p>
<div class="blog_h3"><span class="graybg">常用选项</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 18%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-machine</td>
<td>
<p>指定虚拟的客户机的类型及其属性，选项格式：</p>
<pre class="crayon-plain-tag">-machine [type=]name[,prop=value[,...]]</pre>
<p>其中type为机器类型，可以调用<pre class="crayon-plain-tag">qemu-system-x86_64 -machine help</pre> 获得完整机器类型列表，每种机器都标注了主板芯片组的类型</p>
<p>你可以指定多个可选的属性：</p>
<p>accel=accels1[:accels2[:...]]    启用加速器，可用的包括kvm、xen、tcg，加速器可以指定多个，后面的是备选</p>
</td>
</tr>
<tr>
<td>-cpu</td>
<td>指定虚拟的CPU类型，可以通过<pre class="crayon-plain-tag">qemu-system-x86_64 -cpu help</pre> 查看可用CPU列表</td>
</tr>
<tr>
<td>-smp</td>
<td>
<p>虚拟一个SMP系统，在PC机最多虚拟255CPU，选项格式：</p>
<pre class="crayon-plain-tag">-smp [cpus=]n[,cores=cores][,threads=threads][,sockets=sockets][,maxcpus=maxcpus]</pre>
<p>你可以指定多个属性：<br />cpus    处理器个数<br />cores     每个处理器的核心数<br />threads    每个核心的线程数<br />sockets    CPU插槽数<br />maxcpus    最大可热拔插的CPU数</p>
</td>
</tr>
<tr>
<td>-global</td>
<td>设置驱动属性为指定的值，选项格式：<pre class="crayon-plain-tag">-global driver.prop=value</pre> ，示例：<br />
<pre class="crayon-plain-tag">-global ide-drive.physical_block_size=4096</pre></p>
<p>使用该选项，你可以改变由机型（machine）预定义的设备属性，如果要添加设备，请使用-device </p>
</td>
</tr>
<tr>
<td>-boot</td>
<td>设置客户机的磁盘启动顺序，选项格式：<br />
<pre class="crayon-plain-tag">-boot [order=drives][,once=drives][,menu=on|off][,splash=sp_name]
      [,splash-time=sp_time][,reboot-timeout=rb_timeout][,strict=on|off]</pre></p>
<p>drives值指定为磁盘符号构成的字符串，这些符号的形式取决于客户机的架构，在X86 PC上：<br />        a  软盘1；b 软盘2；c 第一个硬盘；d 第一个光驱；n-p 四个以太网卡</p>
<p>你可以指定多个属性：<br />order    磁盘启动顺序<br />once      仅生效一次的启动顺序<br />menu    交互式启动，显示菜单<br />splash    显示一个开机画面，图片必须是JPEG/BMP格式且分辨率支持SVGA模式<br />splash-time  开机画面显示的时间<br />reboot-timeout    如果启动失败，多少ms后重启</p>
</td>
</tr>
<tr>
<td>-m</td>
<td>设置客户机的内存大小，单位MB</td>
</tr>
<tr>
<td>-mem-path</td>
<td>从一个临时文件来创建客户机内存<br />可以同时指定  -mem-prealloc  来预分配内存</td>
</tr>
<tr>
<td>-soundhw</td>
<td>启用声卡，选项格式：<br />
<pre class="crayon-plain-tag">-soundhw card1[,card2,...]
# 或者
-soundhw all

# 显示可用硬件列表
qemu-system-x86_64 -soundhw help
# 示例
qemu-system-x86_64 -soundhw ac97 disk.img</pre>
</td>
</tr>
<tr>
<td>-balloon</td>
<td>
<p>控制KVM的Automatic Ballooning功能。virtio balloon设备可以减少KVM客户机的内存大小，该特性用于主持客户机内存的over-committing——宿主机只有2G内存的情况下，创建两台2G内存的客户机。只要客户机实际使用的内存不到2G，那么多余的部分就可以返还给宿主机</p>
<p>选项格式：</p>
<pre class="crayon-plain-tag"># 禁用balloon设备
-balloon none
# 启用balloon设备，可以指定一个PCI地址
-balloon virtio[,addr=addr]</pre>
</td>
</tr>
<tr>
<td>-device driver</td>
<td>添加一个设备驱动，并指定驱动属性，可用的属性取决于具体的驱动，选项格式： <br />
<pre class="crayon-plain-tag">-device driver[,prop[=value][,...]]</pre>
<p>要获得可用驱动、属性列表，可以：<pre class="crayon-plain-tag">-device help</pre>  和<pre class="crayon-plain-tag">-device driver,help</pre> </p>
<p>对于连接到PCI总线的设备，可以指定：<br /><pre class="crayon-plain-tag">bus=pci.x</pre> ，此设备连接到第x+1个总线上<br /><pre class="crayon-plain-tag">addr=0xM.0xN</pre>  此设备是总线上的第M个设备，这里使用设备的第N个Function，如果只有一个Function，则.0xN省略</p>
<p>该选项可以用于<span style="background-color: #c0c0c0;">添加客户机的多种虚拟设备并进行细节上的配置（代替部分选项例如-boolean、-net nic）</span>，例如：</p>
<p><pre class="crayon-plain-tag"># 添加e1000网卡，以network0为后端
-device e1000,netdev=network0
# 添加基于Virtio的网卡，等价于 -net nic,model=virtio ...
-device virtio-net-pci,netdev=network0,id=net0,mac=DE:AD:BE:EF:F1:08,bus=pci.0,addr=0x3

# 启用virtio balloon设备（收回客户机空闲内存），等价于-balloon ...
-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x5

# 添加基于Virtio的硬盘（前端）
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x4,drive=drive-virtio-disk0</pre>
</td>
</tr>
<tr>
<td>-name</td>
<td>设置客户机的名称</td>
</tr>
<tr>
<td>-uuid</td>
<td>设置客户机的UUID</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong><em>块设备选项</em></strong></td>
</tr>
<tr>
<td>-fd*</td>
<td>-fda、-fdb指定0、1软盘的镜像文件，你可以使用主机的软盘，例如/dev/fd0</td>
</tr>
<tr>
<td>-hd*</td>
<td>-hda、-hdb、-hdc、-hdd指定0、1、2、3硬盘的镜像</td>
</tr>
<tr>
<td>-cdrom</td>
<td>指定光驱镜像，你可以可以使用主机的光驱，例如/dev/cdrom</td>
</tr>
<tr>
<td>-drive</td>
<td>
<p>定义一个新的磁盘驱动器，选项格式：<pre class="crayon-plain-tag">-drive option[,option[,option[,...]]]</pre> </p>
<p>你可以使用以下子选项：<br />file    在宿主机上的磁盘镜像文件<br />if    指定磁盘接口类型，可用的类型包括ide, scsi, sd, mtd, floppy, pflash, virtio<br />bus=bus,unit=unit 依据bus号、unit号来定义此磁盘驱动器被连接在何处<br />index    依据针对磁盘接口类型的序号来定义此磁盘驱动器被连接在何处<br />media    媒体类型，可选disk或cdrom<br />snapshot    是否启用快照功能，on/off<br />cache    在访问磁盘上的数据块时如何使用宿主机的缓存功能，none/writeback/unsafe/directsync/writethrough<br />aio    threads基于pthread的异步I/O；native基于Linux原生的异步I/O<br />format     指定磁盘格式，而不是去检测file，可选值raw/qcow2<br />serial    分配给磁盘的串号<br />addr    如果if=virtio，该选项指定磁盘控制器的PIC地址<br />readonly    设置磁盘为只读</p>
<p>cache的默认取值是writeback，该选项意味着，一旦数据进入宿主机的页缓存，QEMU就向客户机报告“写入已完成”。如果客户机程序正确的flush磁盘缓存，此选项是安全的。否则，宿主机断电将会导致客户机数据损坏。<br />为了防止上述数据丢失，你可以考虑设置cache为writethrough，这样只有宿主机把缓存刷出到磁盘后QEMU才会报告“写入已完成”，但是这样设置会导致严重的性能问题<br />directsync：类似于writethrough，只是绕过宿主机的页缓存<br />unsafe：宿主机可以缓存客户机的所有disk IO，客户机的sync请求被忽略</p>
<p>代替-cdrom的配置：<pre class="crayon-plain-tag">-drive file=file,index=2,media=cdrom</pre> <br />代替 -hda, -hdb, -hdc, -hdd的配置：</p>
<p><pre class="crayon-plain-tag">-drive file=file,index=0,media=disk
-drive file=file,index=1,media=disk
-drive file=file,index=2,media=disk
-drive file=file,index=3,media=disk</pre>
</td>
</tr>
<tr>
<td>-mtdblock</td>
<td>指定主板内置闪存的镜像文件</td>
</tr>
<tr>
<td>-sd</td>
<td>指定SD卡镜像</td>
</tr>
<tr>
<td>-snapshot</td>
<td>写入到临时文件，而非硬盘镜像文件，这样，原始硬盘文件就不会被改变</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong><em>显示选项</em></strong></td>
</tr>
<tr>
<td>-display</td>
<td>选择一个显示类型：<br />curses    基于curses输出，如果客户机的图形设备支持文本模式，QEMU基于curses/ncurses接口显示输出；如果客户机图形设备运行在图形模式或者不支持文本模式，则不显示<br />none    不显示视频输出，客户机仍然可以模拟一个图形卡但是其输出不会显示给用户。该选项与-nographic不同，后者具有附加效果——改变串口、并口数据的目的地<br />gtk    在一个GTK窗口中显示视频输出<br />vnc    启动一个VNC服务器</td>
</tr>
<tr>
<td>-vnc</td>
<td>配置VNC，例如<pre class="crayon-plain-tag">-vnc 0.0.0.0:10</pre> </td>
</tr>
<tr>
<td>-nographic</td>
<td>
<p>通常情况下，QEMU基于SDL库来显示VGA输出，如果使用该选项，则QEMU成为完全的命令行程序</p>
<p>尽管如此，QEMU还是把虚拟的串口重定向到控制台、与monitor复用。你可以基于串口控制台来调试虚拟机的内核</p>
<p>你可以在console和monitor之间切换</p>
</td>
</tr>
<tr>
<td>-curses</td>
<td>
<p>在文本模式下，直接在当前Terminal显示VGA输出，在图形模式下则什么都不显示，仅提示“1024 x 768 Graphic mode”之类的信息</p>
</td>
</tr>
<tr>
<td>-no-frame</td>
<td>不显示虚拟机窗口的外框、标题栏</td>
</tr>
<tr>
<td>-vga</td>
<td>指定虚拟的VGA显卡类型，可用的包括：<br />cirrus    默认，Cirrus Logic GD5446显卡，对于Windows，所有Win95之后的系统都能够识别此卡<br />std    支持Bochs VBE扩展的标准VGA扩展，如果客户机OS支持VESA 2.0 VBE扩展（例如XP）并且你希望使用高分辨率<br />vmware    Vmware的SVGA-II兼容显卡<br />qxl    使用spice协议时推荐此卡<br />none    禁用VGA显卡</td>
</tr>
<tr>
<td>-full-screen</td>
<td>以全屏模式启动</td>
</tr>
<tr>
<td>-g</td>
<td>设置初始的分辨率和颜色深度，选项格式：<pre class="crayon-plain-tag">-g widthxheight[xdepth]</pre> </td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong><em>网络选项</em></strong></td>
</tr>
<tr>
<td>-net nic</td>
<td>
<p>创建一个新的网卡并把它连接到一个VLAN，选项格式：</p>
<pre class="crayon-plain-tag">-net nic[,vlan=n][,macaddr=mac][,model=type] [,name=name][,addr=addr][,vectors=v] </pre>
<p>你可以指定以下属性：<br />vlan=n    虚拟局域网编号，默认0。虚拟局域网仅在<span style="background-color: #c0c0c0;">同一个QEMU进程内部有效</span>，加入到同一个vlan的网卡可以接收到彼此的数据<br />macaddr 改变此网卡的MAC地址<br />addr    设置设备地址（仅PCI卡）<br />name    设备名称，在monitor命令中使用<br />model    网卡型号，在PC上默认创建e1000。你可以使用<pre class="crayon-plain-tag">-net nic,model=help</pre> 列出所有可用的网卡型号</p>
</td>
</tr>
<tr>
<td>-netdev user<br />-net user</td>
<td>添加一个User网络后端，选项格式：<br />
<pre class="crayon-plain-tag">-netdev user,id=id[,option][,option][,...]
-net user[,option][,option][,...]</pre></p>
<p>你可以指定以下属性：<br />vlan=n    连接到虚拟局域网<br />id,name    设备名称，在monitor命令中使用<br />net=addr[/mask]    在客户机看来，此后端的IP地址，默认10.0.2.0/24<br />host=addr    在客户机看来，宿主机的IP地址是多少，默认段内第二个IP，例如10.0.2.2<br />restrict=on|off    如果启用，则客户机被隔离，这意味着客户机不能访问宿主机或者联网<br />hostname    内置DHCP服务器报告的客户机的名称<br />dhcpstart    内置DHCP服务器能分配的IP地址的其实值，默认段内第15各IP，例如10.0.2.15<br />dns    指定客户机看到的虚拟DNS服务器的地址，默认段内第三个IP，例如10.0.2.3<br />smb=dir[,smbserver=addr]    激活一个内置的SMB服务器，默认地址为段内第四个，例如10.2.2.4<br />hostfwd    重定向主机端口上的incoming TCP/UDP流量到客户机端口</p>
</td>
</tr>
<tr>
<td>-netdev tap<br />-net tap</td>
<td>
<p>添加一个TAP网络后端，连接宿主机的TAP网络接口到VLAN，选项格式：</p>
<pre class="crayon-plain-tag">-netdev tap,id=id[,fd=h][,ifname=name][,script=file][,downscript=dfile][,helper=helper]
-net tap[,vlan=n][,name=name][,fd=h][,ifname=name][,script=file][,downscript=dfile][,helper=helper]</pre>
<p>你可以指定以下属性：<br />id    TAP设备的唯一标识，你只需要指定此标识，QEMU会自动在宿主机上创建对应的TAP设备<br />script    配置脚本，默认/etc/qemu-ifup。设置为no则禁用配置脚本<br />downscript    解除配置脚本，默认/etc/qemu-ifdown（空白文件）。设置为no则禁用配置脚本<br />fd    指定已经打开的宿主机TAP设备的句柄</p>
</td>
</tr>
<tr>
<td>-netdev bridge<br /> -net bridge</td>
<td>添加一个TAP网络后端，连接宿主机的TAP网络接口到宿主机的一个网桥，这是TAP后端的script-free的简化版，选项格式：<br />
<pre class="crayon-plain-tag">-netdev bridge,id=id[,br=bridge][,helper=helper]
-net bridge[,vlan=n][,name=name][,br=bridge][,helper=helper]</pre></p>
<p>你可以指定以下属性：<br />br    宿主机网桥名</p>
<p>此后端需要读取配置文件：</p>
<pre class="crayon-plain-tag">allow br0</pre>
</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><em><strong>字符设备选项</strong></em></td>
</tr>
<tr>
<td>-chardev</td>
<td>
<p>字符设备选项的通用格式为：
<pre class="crayon-plain-tag">-chardev backend ,id=id [,mux=on|off] [,options]</pre>
<p>backend 包括： null, socket, udp, msmouse, vc, ringbuf, file, pipe, console, serial, pty, stdio, braille, tty, parallel, parport, spicevmc.  spiceport<br />id    所有设备都必须有的标识符，可以是任何长度不超过127的字符串<br />mux=on|off    所有字符设备都可以进入多路复用模式，供多个前端使用。Ctrl + A和Ctrl + C用于切换前端<br />options    取决于后端</p>
</td>
</tr>
<tr>
<td>-chardev null</td>
<td>其行为类似于/dev/null</td>
</tr>
<tr>
<td>-chardev vc</td>
<td>
<p>连接到QEMU的文本控制台（text console），选项格式：</p>
<pre class="crayon-plain-tag">-chardev vc ,id=id [[,width=width] [,height=height]] [[,cols=cols] [,rows=rows]] </pre>
<p>width/height    控制台的宽度高度、单位像素</p>
<p>cols/rows    匹配文本控制台宽高</p>
</td>
</tr>
<tr>
<td>-chardev ringbuf</td>
<td>
<p>创建一个固定大小的环形缓冲区，选项格式：<pre class="crayon-plain-tag">-chardev ringbuf ,id=id [,size=size]</pre> </p>
<p>size    必须是2的幂，默认64K</p>
</td>
</tr>
<tr>
<td>-chardev pipe  </td>
<td>创建一个双向的管道文件，选项格式：<pre class="crayon-plain-tag">-chardev pipe ,id=id ,path=path</pre> </td>
</tr>
<tr>
<td>-chardev file</td>
<td>记录来自客户端的流落到文件，选项格式：<pre class="crayon-plain-tag">-chardev file ,id=id ,path=path</pre> </td>
</tr>
<tr>
<td>-chardev console</td>
<td>发送来自客户端的流量到QEMU的标准输出</td>
</tr>
<tr>
<td>-chardev serial</td>
<td>
<p>发送来自客户端的流量到宿主机的一个串口设备，选项格式：<pre class="crayon-plain-tag">-chardev serial ,id=id ,path=path</pre> </p>
<p>path    宿主机的串口设备</p>
</td>
</tr>
<tr>
<td>-chardev pty</td>
<td>在宿主机上创建一个新的伪终端，并连接到它</td>
</tr>
<tr>
<td>-chardev tty</td>
<td>
<p>在Unix-like系统上可用，-chardev serial的别名</p>
<p>所谓TTY，即电传打字机，是由一个键盘、一个打印机组成的设备，在键盘上每打印一个字就会打印到纸张上。这个概念借用到UNIX领域，则打印的目标变成了屏幕。TTY可以用来指任何形式的Terminal，例如伪终端、虚拟控制台</p>
</td>
</tr>
<tr>
<td colspan="2">
<p style="text-align: center;"><strong><em>Linux/Multiboot相关</em></strong></p>
</td>
</tr>
<tr>
<td>-kernel</td>
<td>
<p>指定内核镜像，目标镜像可以是Linux内核或者multiboot格式</p>
</td>
</tr>
<tr>
<td>
<p>-append</p>
</td>
<td>
<p>指定内核命令行参数</p>
</td>
</tr>
<tr>
<td>-initrd</td>
<td>使用指定的文件作为初始内存盘（initial ram disk）</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><em><strong>调试/专家选项</strong></em></td>
</tr>
<tr>
<td>-serial</td>
<td>
<p>-serial dev    重定向虚拟串口到宿主机的字符设备dev，默认设备：图形模式下是vs；非图形模式下是stdio</p>
<p>你可以指定此选项最多4次，模拟最多4个串口；指定 -serial none 禁用所有串口</p>
<p>可用的宿主机字符设备有：<br />vc    虚拟控制台，可选的，指定宽高（像素/字符数）：vc:800x600   vc80C:40Cb<br />stdio     标准输入输出，即启动QEMU的哪个Terminal<br />pty    伪终端<br />none  无设备<br />null    void设备<br />chardev:id    已命名的、通过-chardev选项配置的字符设备<br />/dev/tty    使用宿主机的tty</p>
</td>
</tr>
<tr>
<td>-monitor</td>
<td>-monitor dev 重定向monitor到主机的字符设备dev，可用设备同上</td>
</tr>
<tr>
<td>-qmp</td>
<td>-qmp dev    类似于-monitor但是以control模式开启 </td>
</tr>
<tr>
<td>-debugcon</td>
<td>-debugcon dev    重定向调试控制台到宿主机字符设备</td>
</tr>
<tr>
<td>-pidfile</td>
<td>存储QEMU进程的PID到文件</td>
</tr>
<tr>
<td>-enable-kvm</td>
<td>启用基于KVM的全虚拟化支持 </td>
</tr>
<tr>
<td>-no-reboot</td>
<td>退出而不是重启</td>
</tr>
<tr>
<td>-no-shutdown</td>
<td>当客户机关机时，不退出QEMU而仅仅是停止模拟。你可以切换到Monitor并提交修改到磁盘镜像</td>
</tr>
<tr>
<td>-loadvm</td>
<td>-loadvm file 从一个以保存的状态加载客户机</td>
</tr>
<tr>
<td>-daemonize</td>
<td>
<p>在初始化后，让QEMU变成一个守护进程。使用该选项，可以让QEMU进程和当前Terminal解除关联</p>
<p>此选项在1.4之后不能和<pre class="crayon-plain-tag">-nographic</pre> 联用，但是可以和<pre class="crayon-plain-tag">-display none</pre> 联用</p>
</td>
</tr>
<tr>
<td>-readconfig</td>
<td>从文件读取配置</td>
</tr>
<tr>
<td>-writeconfig </td>
<td>把配置写入到文件，如果指定 - 则打印到屏幕</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">快捷键</span></div>
<p>在图形化模拟期间，你可以使用快捷键：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 18%; text-align: center;">快捷键</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Ctrl-Alt</td>
<td>释放/获取鼠标键盘</td>
</tr>
<tr>
<td>Ctrl-Alt-f</td>
<td>切换全屏模式</td>
</tr>
<tr>
<td>Ctrl-Alt-+</td>
<td>增大屏幕</td>
</tr>
<tr>
<td>Ctrl-Alt--</td>
<td>减小屏幕</td>
</tr>
<tr>
<td>Ctrl-Alt-u</td>
<td>还原原始屏幕大小</td>
</tr>
<tr>
<td>Ctrl-Alt-n</td>
<td>切换到虚拟控制台n，标准控制台为：<br />1    客户机系统的显示<br />2    Monitor<br />3    串口</td>
</tr>
</tbody>
</table>
<p> 如果你使用了-nographic，则可以使用以下快捷键：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 18%; text-align: center;">快捷键</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Ctrl-a h</td>
<td>打印帮助</td>
</tr>
<tr>
<td>Ctrl-a x</td>
<td>退出模拟器</td>
</tr>
<tr>
<td>Ctrl-a s</td>
<td>保存磁盘数据文件（如果使用-snapshot）</td>
</tr>
<tr>
<td>Ctrl-a c</td>
<td>在控制台和Monitor之间切换</td>
</tr>
<tr>
<td>Ctrl-a Ctrl-a</td>
<td>发送Ctrl-a</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">qemu-nbd</span></div>
<p>用于创建QEMU网络块设备（Network Block Device）服务器，即通过NBD协议把磁盘镜像暴露出去。 命令格式：<pre class="crayon-plain-tag">qemu-nbd [OPTION]... diskimgfile</pre> 。</p>
<p>常用选项如下表：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-p</td>
<td>NBD服务监听端口，默认1024</td>
</tr>
<tr>
<td>-b</td>
<td>NBD服务器绑定的网络接口，默认0.0.0.0</td>
</tr>
<tr>
<td>-k</td>
<td>NBD绑定的UNIX socket路径</td>
</tr>
<tr>
<td>-o</td>
<td>访问镜像文件的偏移量</td>
</tr>
<tr>
<td>-f</td>
<td>镜像文件格式</td>
</tr>
<tr>
<td>-r</td>
<td>仅允许只读访问镜像</td>
</tr>
<tr>
<td>-P</td>
<td>--partition=num，仅暴露分区num</td>
</tr>
<tr>
<td>-s</td>
<td>把diskimgfile作为外部快照使用，创建一个新的临时镜像，将其backing_file设置为diskimgfile，写操作都重定向到临时镜像</td>
</tr>
<tr>
<td>-l</td>
<td>--load-snapshot=snapshot_param，加载diskimgfile中的一个内部快照，并暴露其为一个只读设备<br />snapshot_param可以是snapshot.id=id或者snapshot.name=name，或者直接写id/name</td>
</tr>
<tr>
<td>-n</td>
<td>禁用缓存</td>
</tr>
<tr>
<td>--cache=cache</td>
<td>设置缓存模式，支持的模式参考qemu-system-x86 -drive cache=</td>
</tr>
<tr>
<td>--aio=aio</td>
<td>选择AIO模式，threads或者native</td>
</tr>
<tr>
<td>-c</td>
<td>--connect=dev连接diskimgfile到一个NBD设备</td>
</tr>
<tr>
<td>-d</td>
<td>断开指定的设备</td>
</tr>
<tr>
<td>-e</td>
<td>--shared=num  此设备可以被最多num个客户端使用</td>
</tr>
<tr>
<td>-t</td>
<td>即使最后一个连接断开，也不退出</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常见问题 </span></div>
<div class="blog_h2"><span class="graybg">不支持virtio-9p-pci</span></div>
<p>报错信息：'virtio-9p-pci' is not a valid device model name</p>
<p>解决办法：参考下面的脚本构建QEMU：</p>
<pre class="crayon-plain-tag">apt install libattr1-dev
configure --prefix=/usr --enable-virtfs
make &amp;&amp; make install</pre>
<div class="blog_h2"><span class="graybg">不支持SDL</span></div>
<p>报错信息：qemu-system-x86_64: -sdl: SDL support is disabled</p>
<p>解决办法：参考下面的脚本构建QEMU：</p>
<pre class="crayon-plain-tag">sudo apt install libsdl2-dev
./configure --prefix=/usr --enable-virtfs --enable-sdl</pre>
<div class="blog_h2"><span class="graybg">qcow2镜像损坏处理</span></div>
<p>报错信息：Image is corrupt; cannot be opened read/write</p>
<p>解决办法：</p>
<pre class="crayon-plain-tag">qemu-img check -r all /media/alex/v12n2/libvirt/images/xenial-23</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/kvm-qemu-study-note">KVM和QEMU学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/kvm-qemu-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>libvirt学习笔记</title>
		<link>https://blog.gmem.cc/libvirt-study-note</link>
		<comments>https://blog.gmem.cc/libvirt-study-note#comments</comments>
		<pubDate>Tue, 04 Aug 2015 08:26:13 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Virtualization]]></category>
		<category><![CDATA[libvirt]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=13220</guid>
		<description><![CDATA[<p>简介 libvirt是广泛使用的、通用虚拟化管理工具，它提供多种命令行工具、多种语言的编程API。 libvirt的目标是：提供一个通用、稳定的抽象层，来安全有效的远程管理一个节点（node）之上的域（domains），因此它需要提供全套的API来完成管理，这些API必须完成Domain的创建、修改、配置、监控、迁移、停止。 libvirt可以管理的虚拟化机制（hypervisor或container）包括：KVM/QEMU、Xen、LXC、OpenVZ、VirtualBox、VMware ESX/GSX、VMware Workstation/Player、Microsoft Hyper-V、IBM PowerVM。 名词术语 术语 说明 node 一台物理机器  hypervisor  node上面的一个软件层，它能虚拟化node，并在其上建立多个虚拟机  libvirt通过所谓driver和各种不同的hypervisor打交道 domain 运行在受hypervisor管理的虚拟化node之上的一个操作系统，当基于容器虚拟化时，则是一个子系统 安装libvirt libvirt的二进制组件可能已经随操作系统安装，如果没有，你可以： [crayon-69e5911605fa7427477592/] 辅助工具 <a class="read-more" href="https://blog.gmem.cc/libvirt-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/libvirt-study-note">libvirt学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>libvirt是广泛使用的、通用虚拟化管理工具，它提供多种命令行工具、多种语言的编程API。</p>
<p>libvirt的目标是：提供一个通用、稳定的抽象层，来安全有效的远程管理一个节点（node）之上的域（domains），因此它需要提供全套的API来完成管理，这些API必须完成Domain的创建、修改、配置、监控、迁移、停止。</p>
<p>libvirt可以管理的虚拟化机制（hypervisor或container）包括：KVM/QEMU、Xen、LXC、OpenVZ、VirtualBox、VMware ESX/GSX、VMware Workstation/Player、Microsoft Hyper-V、IBM PowerVM。</p>
<div class="blog_h2"><span class="graybg">名词术语</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>node</td>
<td>一台物理机器 </td>
</tr>
<tr>
<td>hypervisor </td>
<td>
<p>node上面的一个软件层，它能虚拟化node，并在其上建立多个虚拟机 </p>
<p>libvirt通过所谓driver和各种不同的hypervisor打交道</p>
</td>
</tr>
<tr>
<td>domain</td>
<td>运行在受hypervisor管理的虚拟化node之上的一个操作系统，当基于容器虚拟化时，则是一个子系统</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">安装libvirt</span></div>
<p>libvirt的二进制组件可能已经随操作系统安装，如果没有，你可以：</p>
<pre class="crayon-plain-tag">sudo apt-get install libvirt-bin</pre>
<div class="blog_h2"><span class="graybg">辅助工具</span></div>
<p>可以安装virt-install，这是一个用来创建基于KVM、XEN或者Linux容器的客户机的工具：</p>
<pre class="crayon-plain-tag">sudo apt-get install virtinst</pre>
<p>可以安装virt-manager，它提供了基于libvirt的图形化管理工具：</p>
<pre class="crayon-plain-tag">sudo apt-get install virt-manager</pre>
<p>可以安装virt-viewer，它用于连接到虚拟机的Graphical Console：</p>
<pre class="crayon-plain-tag"># 安装
sudo apt-get install virt-viewer
# 使用
virt-viewer -c qemu:///system</pre>
<div class="blog_h1"><span class="graybg">使用Virsh</span></div>
<p>virsh是libvirt提供的一个命令行工具，利用它你可以通过命令行，交互式的管理你的虚拟机（Domain）。使用此命令，你可以创建、暂停、关闭domain，可以列出当前的domain。</p>
<p>libvirt会在宿主机上运行一个libvirtd守护进程，此进程可以被本地/远程的virsh调用。libvirtd则可以直接调用qemu-kvm来操控客户机。大部分virsh命令需要libvirtd处于运行状态才可用。</p>
<div class="blog_h2"><span class="graybg">Domain管理</span></div>
<p>使用virsh的define、edit、start、shutdown|destroy、reboot、suspend、resumen、undefined子命令，分别可以定义、编辑、启动、关闭、暂停、唤醒、删除Domain。这些命令比较简单，参考virsh命令详解一节。</p>
<div class="blog_h2"><span class="graybg">快照管理</span></div>
<div class="blog_h3"><span class="graybg">快照分类</span></div>
<p>快照可以分为三个级别：</p>
<ol>
<li>卷管理器（Volume Manager） 级别，例如LVM的Snapsot功能</li>
<li>文件系统级别，常用的Ext3不支持，OCFS2支持</li>
<li>文件级别，Raw格式的镜像不支持快照，qcow2格式则支持，且快照分为两类：
<ol>
<li>内部快照：保存在qcow2文件内部的快照：
<ol>
<li>虚拟机状态快照（VM State snapshot）：整个虚拟机的状态，不仅仅是磁盘</li>
<li>磁盘状态快照（Disk State snapshot）：仅仅针对磁盘的快照</li>
</ol>
</li>
<li>外部快照：将原先（Backing）的qcow2镜像设置为只读，新的改变保存到另外的qcow2文件</li>
</ol>
</li>
</ol>
<div class="blog_h3"><span class="graybg">内存快照</span></div>
<p>使用virsh save / virsh restore命令，可以仅仅将Domain的内存状态保存，然后停止Domain，最后恢复。恢复时假设磁盘没有任何改动：</p>
<pre class="crayon-plain-tag"># 保存内存快照
virsh save fedora-10 fedora-10.vmstate

# 恢复内存快照
virsh restore fedora-10.vmstate</pre>
<div class="blog_h3"><span class="graybg">内部快照</span></div>
<p>内部快照、外部快照使用同一组命令来管理的。这些快照默认包含内存、磁盘、设备等全部状态。内部快照示例：</p>
<pre class="crayon-plain-tag"># 创建一个快照
virsh snapshot-create fedora-10
# Domain snapshot 1473667716 created

# 列出Domain的快照
virsh snapshot-list fedora-10
#  Name                 Creation Time             State
# ------------------------------------------------------------
#  1473667716           2016-09-12 16:08:36 +0800 running

# 创建的是内部快照，可以使用底层命令查看
qemu-img info ~/Vmware/KVM/fedora-10/hda.img</pre>
<p>注意，<span style="background-color: #c0c0c0;">一旦创建了快照，Domain就不能被undefine</span>。</p>
<p>要删除内部快照，可以执行：</p>
<pre class="crayon-plain-tag">virsh snapshot-delete fedora-10 1473667716
# Domain snapshot 1473667716 deleted</pre>
<div class="blog_h3"><span class="graybg">外部快照</span></div>
<p>执行下面的命令创建一个外部快照： </p>
<pre class="crayon-plain-tag"># 这里我们仅针对vda磁盘创建了快照，内存状态没有做快照
snapshot-create-as fedora-10 blankos "Initial snapshot" 
    --diskspec=vda,file=/home/alex/Vmware/KVM/fedora-10/blankos.vda.qcow2 --disk-only --atomic</pre>
<p>现在查看客户机关联的块设备：</p>
<pre class="crayon-plain-tag">virsh domblklist fedora-10
# Target     Source
# ------------------------------------------------
# vda        /home/alex/Vmware/KVM/fedora-10/blankos.vda.qcow2</pre>
<p>可以发现关联性转移到外部快照上了，原先的磁盘镜像成为Backing file。注意：<span style="background-color: #c0c0c0;">Domain的后续写操作都发生在新创建的磁盘上</span></p>
<p>要删除外部快照，执行：</p>
<pre class="crayon-plain-tag">virsh snapshot-delete fedora-10 --metadata blankos</pre>
<div class="blog_h3"><span class="graybg">快照链管理</span></div>
<p>我们来创建三个快照：</p>
<pre class="crayon-plain-tag">DIR=/home/alex/Vmware/KVM/fedora-10
virsh snapshot-create-as fedora-10 snap0 "snap0" --diskspec=vda,file=$DIR/snap0.vda.qcow2 --disk-only --atomic
virsh snapshot-create-as fedora-10 snap1 "snap1" --diskspec=vda,file=$DIR/snap1.vda.qcow2 --disk-only --atomic
virsh snapshot-create-as fedora-10 snap2 "snap2" --diskspec=vda,file=$DIR/snap2.vda.qcow2 --disk-only --atomic</pre>
<p>查看当前快照：</p>
<pre class="crayon-plain-tag"># 默认的，新创建的快照作为当前快照
virsh snapshot-current fedora-10 --name</pre>
<p>查看快照链（Backing chain）：</p>
<pre class="crayon-plain-tag">virsh snapshot-list fedora-10 --tree
# vda.qcow2 是base
# snap0
#   |
#   +- snap1
#       |
#       +- snap2    这个是top</pre>
<p>libvrit支持多种方式来管理磁盘的快照链：</p>
<p><strong>方式一：基于blockcommit，合并到base镜像</strong></p>
<p>我们可以清理快照链条，将snap2、snap1、snap0中的变更都<span style="background-color: #c0c0c0;">提交到</span>vda.qcow2中</p>
<pre class="crayon-plain-tag"># 必须在Domain运行着的情况下执行命令
virsh blockcommit fedora-10 vda --base $DIR/vda.qcow2 --top $DIR/snap2.vda.qcow2 --wait --verbose
# 目前带--delete参数会导致 error: unsupported flags (0x2) in function qemuDomainBlockCommit</pre>
<p>提交后，可以安全的删除快照及其元数据（snapshot-delete --metadata），libvrit是<span style="background-color: #c0c0c0;">分开管理backing链和snapshot列表</span>的。 </p>
<p><strong>方式二：基于blockpull，合并到top镜像</strong></p>
<p>也可以反过来，把<span style="background-color: #c0c0c0;">base一直pull到top位置（必须是叶子节点）的snapshot</span>，然后此snapshot就成为完整的磁盘镜像了（不依赖backing镜像）： </p>
<pre class="crayon-plain-tag">virsh blockpull fedora-10 --path $DIR/snap2.vda.qcow2 --base $DIR/vda.qcow2 --wait –verbose</pre>
<p><strong>方法三：基于blockcopy，可以在线迁移磁盘</strong></p>
<p>首先，需要取消Domain定义，将其变为transient的：</p>
<pre class="crayon-plain-tag"># 导出Domain配置
virsh dumpxml --inactive fedora-10 $DIR/domain.xml
# 取消定义
virsh undefine fedora-10</pre>
<p>然后执行拷贝：</p>
<pre class="crayon-plain-tag"># --shallow 浅拷贝，copy.vda.qcow2与snap2.vda.qcow2将具有相同的backing chain即base ⇦ snap0 ⇦ snap1
# --pivot  操作完成后，此Domain改用copy
virsh blockcopy --domain fedora-10 vda $DIR/copy.vda.qcow2 --wait --verbose --shallow --pivot</pre>
<p>拷贝完成后，瞬时的Domain使用copy继续运行：</p>
<pre class="crayon-plain-tag">virsh domblklist fedora-10
# Target     Source
# ------------------------------------------------
# vda        /home/alex/Vmware/KVM/fedora-10/copy.vda.qcow2 </pre>
<p>而原先的磁盘可以迁移走了。</p>
<div class="blog_h2"><span class="graybg">远程访问</span></div>
<p>要通过virsh来访问远程宿主机上的Domain时，需要提供URI。URI的格式如下：</p>
<pre class="crayon-plain-tag">driver[+transport]://[username@][hostname][:port]/[path][?extraparameters]</pre>
<p>URI各部分说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">部分</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>driver</td>
<td>驱动，不同驱动对应了不同的Hypervisor</td>
</tr>
<tr>
<td>transport</td>
<td>
<p>传输协议，主要包括以下几种：</p>
<ol>
<li>unix，使用Unix Domain Socket，仅能在本地使用，不加密，示例：<br />
<pre class="crayon-plain-tag">qemu+unix:///system?socket=/opt/libvirt/run/libvirt/libvirt-sock </pre>
</li>
<li>ssh，通过SSH隧道进行连接，相当于通过SSH隧道在目标宿主机上执行Unix Domain Socket，示例：<br />
<pre class="crayon-plain-tag">qemu+ssh://root@tokyo.gmem.cc/system</pre>
</li>
<li>tcp，通过TCP进行远程连接，通过DIGEST-MD5进行加密，使用SASL/Kerberos进行身份验证，示例：<br />
<pre class="crayon-plain-tag">qemu+tcp://tokyo.gmem.cc/system </pre>
</li>
<li>tls，类似于tcp，但是使用SSL对TCP进行加密，需要配置密钥和证书，使用SASL/Kerberos进行身份验证，示例：<br />
<pre class="crayon-plain-tag">qemu+tls://tokyo.gmem.cc/system</pre>
</li>
</ol>
</td>
</tr>
</tbody>
</table>
<p>要连接到远程宿主机，可以使用-c选项或者connect子命令：</p>
<pre class="crayon-plain-tag">virsh -c qemu+ssh://root@zircon.local/system</pre>
<div class="blog_h3"><span class="graybg">使用unix传输</span></div>
<p>使用该transport时，需要注意配置文件：</p>
<pre class="crayon-plain-tag"># 这些项都是默认值
unix_sock_group = "libvirtd"
unix_sock_ro_perms = "0777"
unix_sock_rw_perms = "0770"</pre>
<p>也就是说，用户必须加入到libvirtd组，才可以使用unix传输，否则会报错：error: Failed to connect socket to '/var/run/libvirt/libvirt-sock': Permission denied。执行下面的命令添加用户到组：</p>
<pre class="crayon-plain-tag">sudo usermod -a -G libvirtd alex</pre>
<p>注意：连接到qemu时，不指定主机名默认使用<span style="color: #000000;">unix socket。</span></p>
<div class="blog_h3"><span class="graybg">使用tcp传输</span></div>
<p>在目标宿主机上，修改配置文件：</p>
<pre class="crayon-plain-tag"># 启用TCP监听
libvirtd_opts="-d -l"</pre>
<p>然后再修改配置文件：</p>
<pre class="crayon-plain-tag"># 默认TCP监听是禁用的
listen_tcp = 1
# 可以修改监听地址和端口
listen_addr = "0.0.0.0"
tcp_port = "16509"
# 可以不启用验证，但是缺乏安全性，所有流量都是明文
auth_tcp = "none"</pre>
<p>最后重启libvirtd即可。  </p>
<div class="blog_h2"><span class="graybg">资源管理</span></div>
<p>相关文章：<a href="/linux-faq#cgroup">Linux知识集锦 - cgroup</a></p>
<p>libvirt基于cgroup来限制客户机对宿主机资源的访问。libvirt不会尝试加载任何controllers，它只会检测哪些controllers被mount。</p>
<p>QEMU驱动支持cpuset, cpu, memory, blkio, devices这几个controller，修改配置文件/etc/libvirt/qemu.conf可以针对QEMU禁用某些controller。</p>
<p>LXC驱动支持 cpuset, cpu, cpuacct, freezer, memory, blkio,devices 这几个controller， 其中cpuacct, devices, memory是必须的，如果这几个controller没有被mount则容器不会被启动。</p>
<div class="blog_h3"><span class="graybg">cgroups布局</span></div>
<p>libvrit引入两个概念，以方便cgroups管理：</p>
<ol>
<li>partitions：不包含任何进程的cgroup，仅仅包含资源控制规则，它可以包含多个子目录，这些子目录要么是partition要么是consumers</li>
<li>consumers：是包含了<span style="background-color: #c0c0c0;">单个</span>虚拟机/容器进程的cgroup</li>
</ol>
<p>对于不使用systemd的宿主机，consumers命名规则为<pre class="crayon-plain-tag">$VMNAME.libvirt-{qemu,lxc}</pre> ，其中VMNAME为虚拟机的名称。默认的，所有consumer都挂在名为machine的partition下：</p>
<pre class="crayon-plain-tag">ls /sys/fs/cgroup/cpu/machine
# fedora-10.libvirt-qemu  ...</pre>
<p>直到cgroups布局后，你就可以直接读写cgroups文件系统，来控制客户机的资源访问。但是virsh也提供了一些命令在运行时控制资源访问。</p>
<div class="blog_h3"><span class="graybg">资源管理命令</span></div>
<ol>
<li>对于CPU访问控制，可以使用virsh schedinfo命令</li>
<li>对于块设备的访问控制，可以使用virsh blkiotune命令</li>
<li>对于网卡流量的控制，可以使用domiftune或者tc命令</li>
</ol>
<div class="blog_h2"><span class="graybg">virsh命令详解</span></div>
<p>该命令最常见的调用形式为：<pre class="crayon-plain-tag">virsh [OPTION]... &lt;command&gt; &lt;domain&gt; [ARG]...</pre> 。其中：</p>
<ol>
<li>command 是一个virsh子命令</li>
<li>domain 是操控的虚拟机的名称、ID或者UUID</li>
<li>ARG是针对特定子命令的参数</li>
<li>OPTION为一般性选项</li>
</ol>
<div class="blog_h3"><span class="graybg">一般选项</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 10%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-c</td>
<td>--connect URI    连接到指定的URI，而不是默认的连接。此选项的效果如同调用了connect子命令</td>
</tr>
<tr>
<td>-d</td>
<td>--debug LEVEL    设置调试级别，级别范围0-4，默认4</td>
</tr>
<tr>
<td>-k</td>
<td>--keepalive-interval INTERVAL   设置确认服务器连接未断开的心跳的发送间隔，单位秒，设置为0则不检测</td>
</tr>
<tr>
<td>-K</td>
<td>--keepalive-count COUNT    确认连接端口之前，发送心跳的次数</td>
</tr>
<tr>
<td>-l</td>
<td>--log FILE 输出日志到文件</td>
</tr>
<tr>
<td>-q</td>
<td>--quiet    安静模式，避免不必要的信息打印</td>
</tr>
<tr>
<td>-t</td>
<td>--timing    为每个命令打印消耗的时间信息</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">一般子命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>help</td>
<td>
<p>显示帮助信息：</p>
<pre class="crayon-plain-tag"># 列出子命令列表
virsh help
# 显示一个子命令的用法
virsh help define</pre>
</td>
</tr>
<tr>
<td>quit, exit</td>
<td>退出交互式的Terminal</td>
</tr>
<tr>
<td>version</td>
<td>显示版本信息：libvir库版本、API版本、运行中的hypervisor版本</td>
</tr>
<tr>
<td>cd</td>
<td>改变当前目录，禁用与交互式的terminal</td>
</tr>
<tr>
<td>pwd</td>
<td>打印当前目录名</td>
</tr>
<tr>
<td>connect</td>
<td>
<p>connect [URI] [--readonly]
<p>（重）连接到一个hypervisor，URI指明如何连接到hypervisor，例如：<br />xen:///    连接到本地XEN hypervisor<br />qemu:///system    以root身份连接到本地管理QEMU/KVM domain的hypervisor<br />qemu:///session   以普通用户身份连接到本地，管理他自己的QEMU/KVM domain<br />lxc:///    连接到本地的LXC容器</p>
</td>
</tr>
<tr>
<td>uri</td>
<td>打印当前连接到的hypervisor的URI</td>
</tr>
<tr>
<td>hostname</td>
<td>打印hypervisor的主机名</td>
</tr>
<tr>
<td>capabilities</td>
<td>打印一个描述当前连接到的hypervisor的能力（capabilities）的XML文档</td>
</tr>
<tr>
<td>list</td>
<td>列出存在的Domain，如果不指定参数，则打印所有运行中的Domain信息</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Domain子命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>autostart</td>
<td><em><span style="text-decoration: underline;">autostart [--disable] domain</span></em><br />用于配置一个Domain随着宿主机而启动</td>
</tr>
<tr>
<td>console</td>
<td><span style="text-decoration: underline;"><em>console domain [devname] [--safe] [--force]</em></span><br />连接到客户机的虚拟串口控制台：<br />devname  设置为一个备选控制台、串口/并口设备的别名，如果不指定则连接到primary控制台</td>
</tr>
<tr>
<td>create</td>
<td><span style="text-decoration: underline;"><em>create FILE [--console] [--paused] [--autodestroy] [--pass-fds N,M,...]</em></span><br />从XML文件FILE创建一个Domain。创建XML的简便方法是调用<pre class="crayon-plain-tag">dumpxml</pre> 子命令来获得既有实例的XML配置：<br />--paused    新的Domain将会暂停，不指定则运行<br />--console    创建后连接到Domain的console<br />--autodestroy    如果virsh断开到libvirt的连接，则自动销毁此domain</td>
</tr>
<tr>
<td>define</td>
<td><em><span style="text-decoration: underline;">define FILE</span></em><br />从XML文件FILE定义一个Domain，此Domain会注册，但是不会自动启动。如果Domain已经在运行，则对其配置的变更在下次启动时生效</td>
</tr>
<tr>
<td>undefine</td>
<td><span style="text-decoration: underline;"><em>undefine domain [--managed-save] [--snapshots-metadata] [ {--storage volumes | --remove-all-storage} --wipe-storage]</em></span><br />解除一个Domain的定义，如果此Domain正在运行，它会被转换为transient的；如果Domain没有运行，则移除它的配置</td>
</tr>
<tr>
<td>desc</td>
<td><span style="text-decoration: underline;"><em>desc domain [[--live] [--config] | [--current]] [--title] [--edit] [--new-desc  new_desc_msg]</em></span><br />显示或者修改Domain的描述、标题，标题通常比较简短</td>
</tr>
<tr>
<td>destroy</td>
<td><span style="text-decoration: underline;"><em>destroy domain [--graceful]</em></span><br />立即终止一个Domain，客户机将没有反应时间，相当于拔掉机器的电源<br />--graceful    避免极度的手段销毁（SIGKILL），如果客户机一段时间后没有关闭，返回一个错误消息</td>
</tr>
<tr>
<td>reboot</td>
<td><span style="text-decoration: underline;"><em>reboot domain [--mode MODE-LIST]</em></span><br />重启一个Domain，效果类似于执行reboot命令</td>
</tr>
<tr>
<td>reset</td>
<td><em><span style="text-decoration: underline;">reset domain</span></em><br />重置一个Domain，效果类似于按主机上的重置按钮，客户机将没有反应时间</td>
</tr>
<tr>
<td>shutdown</td>
<td><em><span style="text-decoration: underline;">shutdown domain [--mode MODE-LIST]</span></em><br />优雅的关闭Domain，此命令将和客户机协商以关机，因此不一定成功，可能消耗较长时间</td>
</tr>
<tr>
<td>start</td>
<td><span style="text-decoration: underline;"><em>start name-or-uuid [--console] [--paused] [--autodestroy] [--bypass-cache] [--force-boot] [--pass-fds N,M,...]</em></span><br />启动一个已经定义的Domain：<br />--paused    此Domain将会暂停<br />--console    连接到客户机的控制台<br />--autodestroy    当virsh断开到libvirtd的连接后，自动销毁Domain</td>
</tr>
<tr>
<td>suspend</td>
<td><em><span style="text-decoration: underline;">suspend domain</span></em><br />暂停一个运行中的Domain，它会维持在内存中，但不再参与调度</td>
</tr>
<tr>
<td>resume</td>
<td><em><span style="text-decoration: underline;">resume domain</span></em><br />从暂停中恢复</td>
</tr>
<tr>
<td>dumpxml</td>
<td><em><span style="text-decoration: underline;">dumpxml domain [--inactive] [--security-info] [--update-cpu] [--migratable]</span></em><br />输出Domain的XML配置信息到屏幕：<br />--migratable    输出一个可迁移的配置<br />--inactive    dump出Domain下次启动时使用的配置，而不是当前正在使用的配置<br />--update-cpu    根据宿主机的CPU，更新Domain配置中的CPU部分</td>
</tr>
<tr>
<td>edit</td>
<td><span style="text-decoration: underline;"><em>edit domain</em></span><br />编辑一个Domain的XML配置，并在下次启动Domain时生效</td>
</tr>
<tr>
<td>save</td>
<td>
<p><span style="text-decoration: underline;"><em>save domain state-file [--bypass-cache] [--xml file] [{--running | --paused}] [--verbose]<br /></em></span></p>
<p>保存一个运行中的Domain的<span style="background-color: #c0c0c0;">内存（而不是磁盘）</span>状态到一个状态文件中，以便后续恢复。一旦被保存，则<span style="background-color: #c0c0c0;">Domain不再继续运行</span>，分配给Domain的内存可以被其它程序使用。该命令类似于Hibernate功能</p>
<p>state-file    状态文件路径<br />--bypass-cache    不包含文件系统缓存，会加快保存速度<br />--verbose    显示保存进度<br />--running    --paused    在恢复后，将Domain变为运行或暂停状态</p>
<p>可以基于domjobinfo子命令监控进度，或者利用domjobabort子命令取消保存，对当前Terminal发送SIGINT（Ctrl + C）也会取消保存</p>
</td>
</tr>
<tr>
<td>restore</td>
<td>
<p><span style="text-decoration: underline;"><em>restore state-file [--bypass-cache] [--xml file] [{--running | --paused}]</em></span></p>
<p>将Domain从virsh save状态中还原</p>
</td>
</tr>
<tr>
<td>domblkstat</td>
<td>
<p><span style="text-decoration: underline;"><em>domblkstat domain [block-device] [--human]<br /></em></span></p>
<p>输出块设备的统计信息<br />block-device    块设备名称（&lt;target dev='name'/&gt;）或块源文件（&lt;source file='name'/&gt;）<br />--human    输出易读的格式</p>
<p>输出列说明：<br />rd_req    读操作次数<br />rd_bytes    读字节数<br />wr_req    写操作次数<br />wr_bytes    写字节数<br />errs    错误计数<br />flush_operations    刷出磁盘的操作次数<br />rd_total_times    读操作总计消耗ns数<br />wr_total_times    写操作总计消耗ns数<br />flush_total_times    刷出操作总计消耗ns数</p>
<p>举例：<pre class="crayon-plain-tag">virsh domblkstat fedora-10 vda</pre> </p>
</td>
</tr>
<tr>
<td>domblkerror</td>
<td>
<p><span style="text-decoration: underline;"><em>domblkerror domain</em></span></p>
<p>显示块设备错误</p>
</td>
</tr>
<tr>
<td>domblkinfo</td>
<td>
<p><span style="text-decoration: underline;"><em> domblkinfo domain block-device</em></span></p>
<p>显示块设备的尺寸相关信息</p>
</td>
</tr>
<tr>
<td>domblklist</td>
<td>
<p><span style="text-decoration: underline;"><em> domblkinfo domain block-device<br /></em></span></p>
<p>以表格形式打印与Domian相关联的块设备的简要信息</p>
</td>
</tr>
<tr>
<td>blockcommit</td>
<td>
<p><span style="text-decoration: underline;"><em>blockcommit domain path [bandwidth] {[base] | [--shallow]} [top] [--delete] [--wait [--verbose] [--timeout seconds] [--async]]</em></span></p>
<p>减少backing镜像链条的长度，将top（最新的）中的变化提交到backing镜像中去。默认的：</p>
<ol>
<li>此命令flatten整个链条</li>
<li>此命令立即返回，commit操作在后台进行，可以使用blockjob检查进度</li>
</ol>
<p>path    磁盘的全限定路径，&lt;target dev='name'/&gt;的name或者&lt;source file='name'/&gt;的file<br />base top    如果指定之一或都指定，则限制commit操作链条的范围<br />--shallow    提交到top直接的backing镜像<br />--delete     操作完成后，删除被合并的文件<br />--wait    阻塞直到操作完成<br />--timeout    阻塞最多的秒数<br />--verbose    显示进度的详细信息<br />--async    尽快的返回，否则在完成commit后还要等待一些清理操作</p>
</td>
</tr>
<tr>
<td>blockpull</td>
<td>
<p><span style="text-decoration: underline;"><em>blockpull domain path [bandwidth] [base] [--wait [--verbose] [--timeout seconds] [--async]]</em></span></p>
<p>从backing镜像链生成一个磁盘。默认的：</p>
<ol>
<li>此命令flatten整个链条</li>
<li>此命令立即返回，commit操作在后台进行，可以使用blockjob检查进度</li>
</ol>
<p>path    磁盘的全限定路径，&lt;target dev='name'/&gt;的name或者&lt;source file='name'/&gt;的file<br />base   backing链条中的这一成员保留，仅它与top之间的backing镜像被合并到top</p>
</td>
</tr>
<tr>
<td>blockcopy</td>
<td>
<p><span style="text-decoration: underline;"><em>blockcopy domain path dest [bandwidth] [--shallow] [--reuse-external] [--raw] [--wait [--verbose] [{--pivot | --finish}] [--timeout seconds] [--async]]</em></span></p>
<p>拷贝磁盘的backing镜像链到dest。默认的：</p>
<ol>
<li>此命令flatten整个链条</li>
<li>此命令立即返回，commit操作在后台进行，可以使用blockjob检查进度</li>
</ol>
<p>domain    操作针对的Domain<br />path     操作针对的磁盘，全限定路径，&lt;target dev='name'/&gt;的name或者&lt;source file='name'/&gt;的file<br />bandwidth    带宽占用限制，MiB/s<br />--shallow  共享backing链，即对于base ⇦ snap0 ⇦ snap1，dest与src共享base ⇦ snap0<br />–pivot    转移，即拷贝完成后，Domain改用dest，不再使用src<br />--reuse-external    指定该选项则dest必须存在，且内容与resulting backing file等同<br />--raw    指定dest的格式，如果指定--reuse-external，则使用dest文件的格式</p>
<p>该命令主要用途是虚拟机的在线磁盘映像拷贝（live disk image copying）或镜像（mirroring），在存储迁移时很有用。应用场景包括：</p>
<ol>
<li>在线磁盘存储迁移</li>
<li>在线磁盘映像、及其backing链的备份</li>
<li>高效的非共享（ non-shared ）存储迁移</li>
</ol>
<p>一个blockcopy操作可以分为两个阶段：</p>
<ol>
<li>所有的源磁盘内容被拷贝到dest。在此阶段，任务可以被取消，dest的状态没有保证</li>
<li>source、dest的内容变得等同，它们将保持mirrored状态，直到调用blockjob  --abort以结束mirroring</li>
</ol>
</td>
</tr>
<tr>
<td>domifstat</td>
<td>
<p><span style="text-decoration: underline;"><em>domifstat domain interface-device</em></span></p>
<p>输出网络接口的统计信息</p>
<p>输出列说明：<br /> rx_bytes 收字节数<br /> rx_packets收IP封包数<br /> rx_errs 错误封包数<br /> rx_drop 丢弃封包数<br /> tx开头的表示发送的统计数据</p>
<p>举例：<pre class="crayon-plain-tag">virsh domifstat fedora-10 tap0</pre> </p>
</td>
</tr>
<tr>
<td>domif-setlink</td>
<td>
<p><span style="text-decoration: underline;"><em>domif-setlink domain interface-device state [--config]</em></span></p>
<p>修改网络接口的状态<br />state    目标状态，up/down<br />--config    --persistent    仅仅修改Domain的持久化配置，不立即改变接口状态</p>
</td>
</tr>
<tr>
<td>domif-getlink</td>
<td>
<p><span style="text-decoration: underline;"><em>domif-getlink domain interface-device [--config]</em></span></p>
<p>获得网络接口的状态</p>
</td>
</tr>
<tr>
<td>domiflist</td>
<td>
<p><span style="text-decoration: underline;"><em> domiflist domain [--inactive]<br /></em></span></p>
<p>以表格形式打印与Domian相关联的网络接口的简要信息</p>
</td>
</tr>
<tr>
<td>dommemstat</td>
<td>
<p><span style="text-decoration: underline;"><em>dommemstat domain [--period seconds] [[--config] [--live] | [--current]]</em></span></p>
<p>获得运行中的Domain的内存统计信息</p>
</td>
</tr>
<tr>
<td>blkdeviotune</td>
<td>
<p><span style="text-decoration: underline;"><em>blkdeviotune domain device [[--config] [--live] | [--current]] [[total-bytes-sec] | [read-bytes-sec] [write-bytes-sec]] [[total-iops-sec] | [read-iops-sec] [write-iops-sec]]<br /></em></span></p>
<p>查询或者设置Domain的某个块设备的I/O参数：<br />domain   目标客户机<br />device    操作针对的磁盘，&lt;target dev='name'/&gt;的name或者&lt;source file='name'/&gt;的file<br />--total-bytes-sec    设置每秒总计吞吐量的限制，单位Byte<br />--read-bytes-sec    设置每秒读取吞吐量的限制，单位Byte<br />--write-bytes-sec   设置每秒写入吞吐量的限制，单位Byte<br />--total-iops-sec    设置每秒IO操作次数限制<br />--read-iops-sec    设置每秒读次数限制<br />--write-iops-sec   设置每秒写次数限制<br />--live    影响正在运行的客户机<br />--config    在下次重启后，影响非瞬时Domain<br />--current    影响当前客户机的状态</p>
</td>
</tr>
<tr>
<td> domiftune</td>
<td>
<p><em><span style="text-decoration: underline;">domiftune domain interface-device [[--config] [--live] | [--current]] [--inbound average,peak,burst] [--outbound average,peak,burst]</span></em></p>
<p>查询或者修改网络接口的带宽参数：<br />interface-device    目标网络接口<br />--inbound    --outbound   修改入站还是出站带宽参数，如果都不指定，则执行查询操作。average/peak以KiB/s解释，burst以单次burst消耗的KiB解释<br />        average  设置网络接口期望的平均速率<br />        peak 设置峰值速率限制<br />        burst 以峰值速率爆发式的传送数据，最多连续传送多是KiB<br />--live    影响当前正在运行的Domain</p>
<p>举例：<pre class="crayon-plain-tag">virsh domiftune fedora-10 tap0</pre> </p>
</td>
</tr>
<tr>
<td>schedinfo </td>
<td>
<p><span style="text-decoration: underline;"><em>schedinfo domain [[--config] [--live] | [--current]] [[--set] parameter=value]...</em></span></p>
<p><span style="text-decoration: underline;"><em> schedinfo [--weight number] [--cap number] domain</em></span></p>
<p>显示或者设置Domain进程在宿主机中调度参数，可用参数：<br />LXC (posix scheduler)：cpu_shares<br />QEMU/KVM (posix scheduler)：cpu_shares, vcpu_period, vcpu_quota, emulator_period, emulator_quota<br />Xen (credit scheduler)： weight, cap<br />ESX (allocation scheduler)：reservation, limit, shares</p>
<p>--live    影响正在运行的Domain<br />--config    在下次重启后，影响非瞬时Domain<br />--set    执行设置操作</p>
<p>cpu_shares    处理器占用权重，范围0-262144，负值被转换为正值因此-1即262144，超过最大值都相当于262144<br />vcpu_period     对调度进行干预的周期，单位us，范围1000-1000000或者0，100000表示100ms干预一次<br />emulator_period<br />vcpu_quota    在干预周期内，进程能得到的时间片数，单位us，25000表示在一个周期内得到25ms的时间片<br />emulator_quota</p>
</td>
</tr>
<tr>
<td>screenshot</td>
<td>
<p><span style="text-decoration: underline;"><em>screenshot domain [imagefilepath] [--screen screenID]<br /></em></span></p>
<p>对Domain当前Console进行截屏，并保存到文件</p>
</td>
</tr>
<tr>
<td>send-key</td>
<td>
<p><span style="text-decoration: underline;"><em>send-key domain [--codeset codeset] [--holdtime holdtime] keycode..</em></span></p>
<p>将keycode的序列转换为按键动作，并发送到Domainkey，可用参数：<br />code可以是数字或者是codeset中的符号名称<br />--holdtime  每个按键按下持续的毫秒数<br />--codeset 指定代码集，默认Linux</p>
<p>举例：</p>
<pre class="crayon-plain-tag"># 发送右侧Ctrl + C到fedora-10
virsh send-key fedora-10 KEY_RIGHTCTRL KEY_C
# 发送Ctrl + Alt + Del
virsh send-key debian-20 KEY_LEFTCTRL KEY_LEFTALT KEY_DELETE
# 发送TAB，按下1秒
virsh send-key fedora-10 --holdtime 1000 0xf </pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">迁移子命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>migrate</td>
<td>
<p><span style="text-decoration: underline;"><em>migrate [--live] [--offline] [--direct] [--p2p [--tunnelled]] [--persistent] [--undefinesource] [--suspend] [--copy-storage-all] [--copy-storage-inc] [--change-protection] [--unsafe] [--verbose] [--compressed] [--abort-on-error] domain desturi [migrateuri] [graphicsuri] [listen-address] [dname] [--timeout seconds] [--xml file]</em></span>
<p>将客户机迁移到另外一台宿主机上，可用参数：<br />--live  在线迁移，迁移期间，源宿主机上的客户机不被暂停<br />--p2p  使用点对点迁移<br />--direct  使用直接迁移<br />--tunnelled  使用隧道迁移<br />--offline  不在目标机器上启动客户机，也不再源机器上停止客户机。通常用于inactive客户机的迁移<br />--persistent  让Domain在目标机器上持久化<br />--undefinesource  取消Domain在源机器上的定义<br />--suspend  让客户机在目标机器上停留在suspend状态<br />--copy-storage-all  提示不使用共享存储，进行完整的磁盘拷贝<br />--copy-storage-inc  提示不使用共享存储，进行增量的磁盘拷贝（即源、目标的共享backing file不拷贝）<br />注意：上面两个参数要求目标镜像文件存在于目标机器的对应位置<br />--change-protection  确保在迁移完成前，不得对Domain执行不兼容的配置更改。如果Hypervisor支持，该参数会自动包含，手工指定此参数的话，如果Hypervisor不支持change protection，则迁移操作被libvirt拒绝<br />--verbose  显示迁移进度<br />--compressed  对于需要在迁移过程中反复传输的内存页，执行压缩<br />--abort-on-error  当发生软错误（Soft error，例如I/O错误）时取消迁移<br />--unsafe  允许不安全的迁移（可能导致数据损坏）<br />desturi  目标宿主机的连接URI。对于点对点迁移，该参数是源宿主机看到的目标主机的URI；对于普通迁移则是客户端看到的URI<br />domain  被迁移的客户机名称<br />dname   在迁移时，修改Domain的名称<br />--xml  在目标机器上，为Domain指定机器特定的Domain配置信息，例如存储卷的名称（对于同一底层存储两台机器的命名可能不同）<br />--timeout  如果在线迁移超过指定的秒数，则Domain被强制suspend，然后转入离线迁移，必须和--live联用<br />listen-address  设置目标主机上的Hypervisor监听迁移请求的地址和端口</p>
<p>注意：</p>
<ol>
<li>Hypervisor可能不支持所有的迁移类型，例如QEMU不支持直接迁移</li>
<li>如果迁移可能导致数据损坏，libvirt可能拒绝迁移请求。例如对于QEMU，除非设置磁盘的缓存模式（cache mode）为none或者存储位于一致性的集群文件系统（GFS或者GPFS），迁移会被拒绝。这时可以使用--unsafe强制迁移</li>
</ol>
</td>
</tr>
<tr>
<td>migrate-compcache</td>
<td>
<p><span style="text-decoration: underline;"><em> migrate-compcache domain [--size bytes]<br /></em></span></p>
<p>设置或者取得在线迁移过程中，重复的用来压缩被传输的内存页的缓存的大小，单位字节<br />--size 如果指定此参数，则为设置操作，否则为读取操作</p>
</td>
</tr>
<tr>
<td>migrate-setmaxdowntime</td>
<td>
<p><span style="text-decoration: underline;"><em> migrate-setmaxdowntime domain downtime</em></span></p>
<p>设置domain在线迁移时，能够容忍的最大宕机时间，单位毫秒</p>
</td>
</tr>
<tr>
<td>migrate-compcache</td>
<td> </td>
</tr>
<tr>
<td>migrate-setspeed</td>
<td>
<p><span style="text-decoration: underline;"><em> migrate-setspeed domain bandwidth</em></span></p>
<p>设置domain迁移到其它宿主机上时，最大使用的带宽，单位MiB/s</p>
</td>
</tr>
<tr>
<td>migrate-getspeed</td>
<td>
<p><span style="text-decoration: underline;"><em> migrate-getspeed domain</em></span></p>
<p>获取domain迁移时的最大带宽</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">设备子命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>attach-device</td>
<td>
<p><span style="text-decoration: underline;"><em> attach-device domain FILE [[[--live] [--config] | [--current]] | [--persistent]]</em></span></p>
<p>为domain添加一个新的设备<br />FILE  设备的XML配置，以interface/disk之类的元素为根元素<br />--config   在永久Domian下次启动时生效<br />--live  影响运行中的Domain<br />--current  影响当前Domain状态<br />注意：--config --live可以联用，但是--current不能和它们联用</p>
</td>
</tr>
<tr>
<td>attach-disk</td>
<td>
<p><span style="text-decoration: underline;"><em>attach-disk domain source target [[[--live] [--config] | [--current]] | [--persistent]] [--driver driver] [--subdriver subdriver] [--cache cache] [--type type] [--mode</em></span><br /><span style="text-decoration: underline;"><em> mode] [--config] [--sourcetype soucetype] [--serial serial] [--wwn wwn] [--rawio] [--address address] [--multifunction] [--print-xml]</em></span></p>
<p>添加一个磁盘，参数与磁盘的XML配置元素对应</p>
</td>
</tr>
<tr>
<td>attach-interface</td>
<td>
<p><span style="text-decoration: underline;"><em>attach-interface domain type source [[[--live] [--config] | [--current]] | [--persistent]] [--target target] [--mac mac] [--script script] [--model model] [--config]</em></span><br /><span style="text-decoration: underline;"><em> [--inbound average,peak,burst] [--outbound average,peak,burst]</em></span></p>
<p>添加一个磁盘，参数与网络接口的XML配置元素对应</p>
</td>
</tr>
<tr>
<td>detach-device</td>
<td>
<p><span style="text-decoration: underline;"><em>detach-device domain FILE [[[--live] [--config] | [--current]] | [--persistent]]</em></span></p>
<p>移除一个设备</p>
</td>
</tr>
<tr>
<td>detach-disk</td>
<td>
<p><span style="text-decoration: underline;"><em> detach-disk domain target [[[--live] [--config] | [--current]] | [--persistent]]</em></span></p>
<p>移除一个磁盘</p>
</td>
</tr>
<tr>
<td>detach-interface</td>
<td>
<p><span style="text-decoration: underline;"><em> detach-interface domain type [--mac mac] [[[--live] [--config] | [--current]] | [--persistent]]</em></span></p>
<p>移除一个网络接口</p>
</td>
</tr>
<tr>
<td>update-device</td>
<td>
<p><span style="text-decoration: underline;"><em> update-device domain file [--force] [[[--live] [--config] | [--current]] | [--persistent]]</em></span></p>
<p>更新设备的配置，可用参数：<br />--force  强制设备更新</p>
</td>
</tr>
<tr>
<td>change-media</td>
<td>
<p><span style="text-decoration: underline;"><em> change-media domain path [--eject] [--insert] [--update] [source] [--force] [[--live] [--config] | [--current]]</em></span></p>
<p>改变光驱或者软驱的媒体（盘），可用参数：<br />path  光驱/软驱的全限定路径或者名称，例如hdc<br />source   更换的新媒体镜像的路径<br />--eject  弹出媒介<br />--insert  插入媒介</p>
<p>举例：</p>
<pre class="crayon-plain-tag">virsh # domblklist debian-20
# Target     Source
# ------------------------------------------------
# vda        /home/alex/Vmware/KVM/debian-20/vda.qcow2
# hdd        /home/alex/Software/OS/debian-8.6.0-amd64-netinst.iso

change-media debian-20 --eject --live</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">虚拟网络子命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>net-autostart</td>
<td>
<p><span style="text-decoration: underline;"><em>net-autostart network [--disable]</em></span>
<p>启用/禁用虚拟网络的自动启动</p>
</td>
</tr>
<tr>
<td>net-create</td>
<td>
<p><span style="text-decoration: underline;"><em>net-create file</em></span></p>
<p>从XML配置创建一个临时的虚拟网络并立即启动</p>
</td>
</tr>
<tr>
<td>net-define</td>
<td>
<p><span style="text-decoration: underline;"><em>net-define file<br /></em></span></p>
<p>从XML配置文件创建一个永久的虚拟网络</p>
</td>
</tr>
<tr>
<td>net-destroy</td>
<td>
<p><span style="text-decoration: underline;"><em>net-destroy network</em></span></p>
<p>根据名称或者UUID停止一个虚拟网络</p>
</td>
</tr>
<tr>
<td>net-dumpxml</td>
<td>
<p><span style="text-decoration: underline;"><em>net-dumpxml network [--inactive]</em></span></p>
<p>导出虚拟网络的XML配置</p>
</td>
</tr>
<tr>
<td>net-edit</td>
<td>
<p><span style="text-decoration: underline;"><em>net-edit network</em></span></p>
<p>编辑一个虚拟网络的配置</p>
</td>
</tr>
<tr>
<td>net-info</td>
<td>
<p><span style="text-decoration: underline;"><em> net-info network</em></span></p>
<p>显示一个虚拟网络的详细信息</p>
</td>
</tr>
<tr>
<td>net-list</td>
<td>
<p><span style="text-decoration: underline;"><em> net-list [--inactive | --all] [--persistent] [&lt;--transient&gt;] [--autostart] [&lt;--no-autostart&gt;]<br /></em></span></p>
<p>显示虚拟网络的列表</p>
</td>
</tr>
<tr>
<td>net-start</td>
<td>
<p><span style="text-decoration: underline;"><em>net-start network</em></span></p>
<p>启动一个虚拟网络</p>
</td>
</tr>
<tr>
<td>net-undefine</td>
<td>
<p><span style="text-decoration: underline;"><em> net-undefine network<br /></em></span></p>
<p>取消虚拟网络的定义</p>
</td>
</tr>
<tr>
<td>net-update</td>
<td>
<p><span style="text-decoration: underline;"><em> net-update network command section xml [--parent-index index] [[--live] [--config] | [--current]]<br /></em></span></p>
<p>更新虚拟网络的指定配置片断，可用参数：<br />section 片断名称：bridge, domain, ip, ip-dhcp-host, ip-dhcp-range, forward, forward-interface, forward-pf, portgroup, dns-host, dns-srv，这些名称对应了虚拟网络XML配置的相应子代元素<br />xml  配置片断，要么是XML片断文本，要么是包含XML片断的文件名称<br />--live  影响正在运行的虚拟网络<br />--config  在永久虚拟网络重启后生效</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">快照子命令</span></div>
<p>这些子命令用来管理Domain的快照，快照是Domain的磁盘、内存、设备在某一个时刻的状态，这些状态可以在未来恢复。每个快照由唯一性的名字来识别。</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>snapshot-create</td>
<td>
<p><span style="text-decoration: underline;"><em>snapshot-create domain [xmlfile] {[--redefine [--current]] | [--no-metadata] [--halt] [--disk-only] [--reuse-external] [--quiesce] [--atomic] [--live]}</em></span></p>
<p>为domain创建一个快照，<span style="background-color: #c0c0c0;">创建快照期间客户机通常处于暂停状态</span>。新创建的快照将成为当前快照，可以通过子命令snapshot-current查看。子命令参数：<br /><em>xmlfile</em>    指定此快照的属性，一般仅仅包含name、description元素，如果指定--disk-only则可以包含disks元素<br /><em>--halt</em>    Domain在创建快照后，进入停止（inactive）状态<br /><em>--disk-only</em>    仅仅对磁盘进行快照，与--halt联用则所有没有flush到磁盘的数据丢失<br /><em>--redefine</em>    如果指定，则snapshot-dumpxml生成的所有元素均有效，可用于跨机器迁移快照层次<br /><em>--no-metadata</em>    丢弃源数据，快照不被标记为current，除非后续使用--redefine，无法恢复快照<br /><em>--reuse-external</em>    重用此文件指向的外部快照，目标文件必须存在<br /><em>--live</em>    创建快照时，客户机将处于运行状态，仅支virsh snapshot-create-as fedora-10 snap2 "snap2" --diskspec=vda,file=$DIR/snap2.vda.qcow2 --disk-only --atomic<br />持外部检查点</p>
</td>
</tr>
<tr>
<td>snapshot-create-as</td>
<td>
<p><span style="text-decoration: underline;"><em>snapshot-create-as domain {[--print-xml] | [--no-metadata] [--halt] [--reuse-external]} [name] [description] [--disk-only [--quiesce]] [--atomic] [[--live] [--memspec memspec]] [--diskspec] diskspec]...</em></span></p>
<p>以指定的name和description创建快照<br /><em>--print-xml</em>    仅创建snapshot-create使用的xmlfile，不进行快照创建<br /><em>--memspec=[file=]name[,snapshot=type]</em>    控制内存快照的创建方式，type可取值none,internal,external<br /><em>--diskspec=disk[,snapshot=type][,driver=type][,file=name]</em>    控制--disk-only和外部检查点创建外部文件的方式。此参数可以出现多次（对应Domain配置中磁盘的个数）<br /><em>--atomic</em>  libvrit保证原子性操作，快照要么完整的创建成功，要么彻底失败</p>
</td>
</tr>
<tr>
<td>snapshot-current</td>
<td>
<p><span style="text-decoration: underline;"><em>snapshot-current domain {[--name] | [--security-info] | [snapshotname]}</em></span></p>
<p>查看和设置当前快照：如果不指定snapshotname，则输出Domain当前快照的XML。否则把snapshotname设置为当前快照<br /><em>--name</em>    仅仅输出当前快照的名称，而非XML<br /><em>--security-info</em>    在XML中包含安全性敏感的信息</p>
</td>
</tr>
<tr>
<td>snapshot-edit</td>
<td>
<p><em><span style="text-decoration: underline;">snapshot-edit domain [snapshotname] [--current] {[--rename] | [--clone]}</span></em></p>
<p>编辑snapshotname的XML信息<br /><em>--current</em>    编辑当前快照，如果同时指定snapshotname，则snapshotname被设置为当前快照<br /><em>--rename</em>    允许设置快照名称<br /><em>--clone</em>    改变快照名称后，会创建一份快照元数据的克隆</p>
</td>
</tr>
<tr>
<td>snapshot-info</td>
<td>
<p><span style="text-decoration: underline;"><em>snapshot-info domain {snapshot | --current}</em></span></p>
<p>输出当前快照或者指定快照的基本信息</p>
</td>
</tr>
<tr>
<td>snapshot-list</td>
<td>
<p><span style="text-decoration: underline;"><em>snapshot-list domain [--metadata] [--no-metadata] [{--parent | --roots | [{--tree | --name}]}] [{[--from] snapshot | --current} [--descendants]] [--leaves] [--no-leaves] p[--inactive] [--active] [--disk-only] [--internal] [--external]</em></span></p>
<p>列出Domain所有可用的快照，默认输出列：快照名称、创建时间、Domain的状态</p>
<p><em>--parent</em>   输出parent列，显示父快照名称<br /><em>--roots</em>    仅列出没有parent快照的那些快照<br /><em>--tree</em>    以树状输出结果，显示快照父子关系<br /><em>--name</em>    仅仅显示快照名称，与--tree互斥<br /><em>--from</em>    仅仅显示指定快照的子快照，联用--current则显示当前快照的子快照<br /><em>--descendants</em>    包含所有后代快照<br /><em>--leaves</em>    仅仅输出没有子代的那些快照<br /><em>--inactive    --active </em>   根据快照中Domain的状态过滤<br /><em>--internal   --external</em>    根据内/外部快照过滤</p>
</td>
</tr>
<tr>
<td>snapshot-dumpxml</td>
<td>
<p><span style="text-decoration: underline;"><em>snapshot-dumpxml domain snapshot [--security-info]<br /></em></span></p>
<p>显示指定快照的XML</p>
</td>
</tr>
<tr>
<td>snapshot-parent</td>
<td>
<p><span style="text-decoration: underline;"><em>snapshot-parent domain {snapshot | --current}</em></span></p>
<p>显示父快照的名称</p>
</td>
</tr>
<tr>
<td>snapshot-revert</td>
<td>
<p><span style="text-decoration: underline;"><em>snapshot-revert domain {snapshot | --current} [{--running | --paused}] [--force]</em></span></p>
<p>恢复Domain到指定的快照状态，此最后一次快照以来对Domain的变更将消失<br /><em>--running    --paused</em> 通常恢复后Domain处于创建快照时的状态，这两个参数可以覆盖之<br /><em>--force</em>    强制恢复快照</p>
</td>
</tr>
<tr>
<td>snapshot-delete</td>
<td>
<p><span style="text-decoration: underline;"><em>snapshot-delete domain {snapshot | --current} [--metadata] [{--children | --children-only}]<br /></em></span></p>
<p>删除Domian的快照<br /><em>--children</em>   删除指定的快照及其子代<br /><em>--children-only</em>    仅仅删除子代，指定的快照本身被保留<br /><em>--metadata</em>    仅仅删除libvirt管理的元数据，不理会快照文件</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">存储池子命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>find-storage-pool-sources</td>
<td>
<p><span style="text-decoration: underline;"><em>find-storage-pool-sources type [srcSpec]</em></span></p>
<p>返回一个XML，描述所有能够找到的type类型的存储池<br />srcSpec  包含额外限制条件的XML</p>
</td>
</tr>
<tr>
<td>find-storage-pool-sources-as</td>
<td>
<p><span style="text-decoration: underline;"><em>find-storage-pool-sources-as type [host] [port] [initiator]</em></span></p>
<p>类似上面，host port initiator限制查询条件</p>
</td>
</tr>
<tr>
<td>pool-autostart</td>
<td>
<p><span style="text-decoration: underline;"><em>pool-autostart pool-or-uuid [--disable]</em></span></p>
<p>配置存储池的自动启动</p>
</td>
</tr>
<tr>
<td>pool-build</td>
<td>
<p><span style="text-decoration: underline;"><em> pool-build pool-or-uuid [--overwrite] [--no-overwrite]</em></span></p>
<p>构建一个存储池<br />--overwrite  --no-overwrite 仅仅用于文件系统池，指定覆盖时，如果文件系统已经存在于目标设备上，mkfs也会执行，既有文件系统将被破坏</p>
</td>
</tr>
<tr>
<td>pool-create</td>
<td>
<p><span style="text-decoration: underline;"><em>pool-create file</em></span></p>
<p>从配置文件定义并启动一个存储池</p>
</td>
</tr>
<tr>
<td>pool-create-as</td>
<td>
<p><span style="text-decoration: underline;"><em>pool-create-as name --print-xml type [source-host] [source-path] [source-dev] [source-name] [&lt;target&gt;] [--source-format format]</em></span></p>
<p>类似上面，但是通过命令行来指定所需参数：<br />--print-xml  打印生成的池的XML配置</p>
</td>
</tr>
<tr>
<td>pool-define</td>
<td>
<p><em><span style="text-decoration: underline;"> pool-define file</span></em></p>
<p>定义，但不启动池</p>
</td>
</tr>
<tr>
<td>pool-define-as</td>
<td>
<p><span style="text-decoration: underline;"><em> pool-define-as name --print-xml type [source-host] [source-path] [source-dev] [source-name] [&lt;target&gt;] [--source-format format]</em></span></p>
<p>类似上面，但是通过命令行来指定所需参数：<br />--print-xml 打印生成的池的XML配置</p>
</td>
</tr>
<tr>
<td>pool-destroy</td>
<td>
<p><span style="text-decoration: underline;"><em>pool-destroy pool-or-uuid</em></span></p>
<p>停止一个存储池，池中的数据不会消失</p>
</td>
</tr>
<tr>
<td>pool-delete</td>
<td>
<p><span style="text-decoration: underline;"><em>pool-delete pool-or-uuid</em></span></p>
<p>销毁池使用的所有资源，但是池本身仍然存在，你可以在其中存储新的卷</p>
</td>
</tr>
<tr>
<td>pool-dumpxml</td>
<td>
<p><span style="text-decoration: underline;"><em>pool-dumpxml [--inactive] pool-or-uuid</em></span></p>
<p>打印池的配置信息</p>
</td>
</tr>
<tr>
<td>pool-edit</td>
<td>
<p><span style="text-decoration: underline;"><em>pool-edit pool-or-uuid</em></span></p>
<p>编辑池的XML配置</p>
</td>
</tr>
<tr>
<td>pool-info</td>
<td>
<p><span style="text-decoration: underline;"><em> pool-info pool-or-uuid<br /></em></span></p>
<p>显示池的基本信息</p>
</td>
</tr>
<tr>
<td>pool-list</td>
<td>
<p><span style="text-decoration: underline;"><em> pool-list [--inactive] [--all] [--persistent] [--transient] [--autostart] [--no-autostart] [[--details] [&lt;type&gt;]</em></span></p>
<p>列出libvirt所知道的全部存储池</p>
</td>
</tr>
<tr>
<td>pool-name<br />pool-uuid</td>
<td>
<p><span style="text-decoration: underline;"><em>pool-name uuid <br />pool-uuid pool</em></span></p>
<p>显示指定uuid对应的池名称，或者显示池名称对应的UUID </p>
</td>
</tr>
<tr>
<td>pool-refresh</td>
<td>
<p><span style="text-decoration: underline;"><em>pool-refresh pool-or-uuid</em></span></p>
<p>刷新池中卷的列表</p>
</td>
</tr>
<tr>
<td>pool-start</td>
<td>
<p><span style="text-decoration: underline;"><em> pool-start pool-or-uuid</em></span></p>
<p>启动一个存储池</p>
</td>
</tr>
<tr>
<td>pool-undefine</td>
<td>
<p><span style="text-decoration: underline;"><em> pool-undefine pool-or-uuid</em></span></p>
<p>解除一个存储池的定义</p>
</td>
</tr>
<tr>
<td>vol-create</td>
<td>
<p><span style="text-decoration: underline;"><em>vol-create pool-or-uuid FILE [--prealloc-metadata]</em></span></p>
<p>在池中创建一个卷，对于基于目录/文件系统的池，卷的本质就是一个镜像文件。可用参数：<br />pool-or-uuid 池的名称或者UUID<br />FILE  卷的配置XML<br />--prealloc-metadata  预分配元数据，用于qcow2之类不支持完全分配的镜像格式，该参数可以提高性能</p>
<p>示例：<pre class="crayon-plain-tag">vol-create default definitions/volumes/fedora-10.xml</pre> </p>
</td>
</tr>
<tr>
<td>vol-create-from</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-create-from pool-or-uuid FILE [--inputpool pool-or-uuid] vol-name-or-key-or-path [--prealloc-metadata]<br /></em></span></p>
<p>创建一个卷，使用另外一个卷vol-name-or-key-or-path作为输入</p>
</td>
</tr>
<tr>
<td>vol-create-as</td>
<td>
<p><span style="text-decoration: underline;"><em>vol-create-as pool-or-uuid name capacity [--allocation size] [--format string] [--backing-vol vol-name-or-key-or-path] [--backing-vol-format string] [--prealloc-metadata]<br /></em></span></p>
<p>创建一个卷，从命令行参数读取配置信息</p>
<pre class="crayon-plain-tag">virsh vol-create-as v12n1 centos7-base 128G --format qcow2</pre>
</td>
</tr>
<tr>
<td>vol-clone</td>
<td>
<p><span style="text-decoration: underline;"><em>vol-clone [--pool pool-or-uuid] vol-name-or-key-or-path name [--prealloc-metadata]</em></span>
<p>克隆一个既有的卷，没有vol-create-from强大但是比它简单。可用参数：<br />--pool  指定在其中创建新卷的池<br />vol-name-or-key-or-path  被克隆的卷<br />name  新的卷的名称</p>
</td>
</tr>
<tr>
<td>vol-delete</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-delete [--pool pool-or-uuid] vol-name-or-key-or-path<br /></em></span></p>
<p>删除一个卷，底层的镜像文件将被删除，示例：</p>
<pre class="crayon-plain-tag">vol-delete --pool default fedora-10.qcow2</pre>
</td>
</tr>
<tr>
<td>vol-upload</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-upload [--pool pool-or-uuid] [--offset bytes] [--length bytes] vol-name-or-key-or-path local-file<br /></em></span>
<p>上传本地文件的内容到一个卷，可用参数：<br />--pool  目标存储池<br />vol-name-or-key-or-path  目标卷<br />local-file  被上传的本地文件<br />--offset  在存储卷的什么位置开始写入数据<br />--length  写入数据的长度</p>
</td>
</tr>
<tr>
<td>vol-download</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-download [--pool pool-or-uuid] [--offset bytes] [--length bytes] vol-name-or-key-or-path local-file<br /></em></span></p>
<p>下载存储卷中的内容到本地文件</p>
</td>
</tr>
<tr>
<td>vol-wipe</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-wipe [--pool pool-or-uuid] [--algorithm algorithm] vol-name-or-key-or-path<br /></em></span></p>
<p>擦除一个卷的内容，可用参数：<br />--algorithm  擦除算法：zero nnsa dod bsi gutmann schneier pfitzner7 pfitzner33 random</p>
</td>
</tr>
<tr>
<td>vol-dumpxml</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-dumpxml [--pool pool-or-uuid] vol-name-or-key-or-path<br /></em></span></p>
<p>打印卷的XML配置，示例：</p>
<pre class="crayon-plain-tag">vol-dumpxml --pool default coreos.qcow2</pre>
</td>
</tr>
<tr>
<td>vol-info</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-info [--pool pool-or-uuid] vol-name-or-key-or-path<br /></em></span>
<p>显示卷的基本信息</p>
</td>
</tr>
<tr>
<td>vol-list</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-list [--pool pool-or-uuid] [--details]<br /></em></span></p>
<p>列出一个存储池中的卷，示例：</p>
<pre class="crayon-plain-tag">vol-list default</pre>
</td>
</tr>
<tr>
<td>vol-pool</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-pool [--uuid] vol-key-or-path<br /></em></span>
<p>根据卷的名称或者路径，返回其所在存储池的信息<br />--uuid 返回UUID而不是池名称</p>
</td>
</tr>
<tr>
<td>vol-path</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-path [--pool pool-or-uuid] vol-name-or-key<br /></em></span></p>
<p>返回指定卷的路径</p>
</td>
</tr>
<tr>
<td>vol-name<br />vol-key</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-name vol-key-or-path<br /> vol-key [--pool pool-or-uuid] vol-name-or-path<br /></em></span></p>
<p>查询卷的名称或者key</p>
</td>
</tr>
<tr>
<td>vol-resize</td>
<td>
<p><span style="text-decoration: underline;"><em> vol-resize [--pool pool-or-uuid] vol-name-or-path pool-or-uuid capacity [--allocate] [--delta] [--shrink]<br /></em></span></p>
<p>重新设定卷的容量，以字节为单位，可用参数：<br />--allocate  分配空间，否则新容量是稀疏的<br />capacity  新的容量，如果指定了--delta，则是增加的容量<br />--shrink  缩小卷大小，不指定此参数，降低容量会报错</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">结合QEMU/KVM</span></div>
<p>模拟器QEMU和hypervisor KVM可以被libvirt管理。</p>
<div class="blog_h2"><span class="graybg">QEMU驱动</span></div>
<p>如果driver检测到/usr/bin/qemu-system-*则QEMU可用；如果driver检测到设备节点/dev/kvm和可执行文件/usr/bin/qemu-kvm则支持KVM全虚拟化和客户机硬件加速。</p>
<p>QEMU的驱动是一个多实例驱动，包含一个系统级别的特权驱动（system实例）和多个用户级别的非特权驱动。驱动的URI的协议名为qemu，URI示例：</p>
<pre class="crayon-plain-tag"># 本地访问per-user的实例
qemu:///session
# 本地访问per-user的实例
qemu+unix:///session
# 本地访问系统级实例
qemu:///system
# 本地访问系统级实例
qemu+unix:///system 
# 基于 TLS/x50的远程访问
qemu://example.com/system 
# 基于SSH隧道远程访问
qemu+ssh://root@example.com/system</pre>
<div class="blog_h2"><span class="graybg">导入/出QEMU配置</span></div>
<div class="blog_h3"><span class="graybg">导入</span></div>
<p><pre class="crayon-plain-tag">virsh domxml-from-native</pre> 命令可以将QEMU命令行选项转换为libvirt的Domain配置格式：</p>
<ol>
<li>将QEMU命令行保存到文件qemu.cmd：<pre class="crayon-plain-tag">/usr/bin/qemu-system-x86_64 -name fedora-10 -enable-kvm -cpu Haswell -daemonize -display none -m 512 -drive file=/home/alex/Vmware/KVM/fedora-10/hda.img,index=0,media=disk,if=virtio -netdev bridge,id=tap0,br=br0 -device virtio-net-pci,netdev=tap0,mac=DE:AD:BE:EF:F1:00</pre> </li>
<li>执行命令：<pre class="crayon-plain-tag">virsh domxml-from-native qemu-argv ~/Vmware/KVM/fedora-10/qemu.cmd ~/Vmware/KVM/fedora-10/domain.xml</pre> </li>
</ol>
<p>生成的配置文件内容如下：</p>
<pre class="crayon-plain-tag">&lt;domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'&gt;
  &lt;name&gt;fedora-10&lt;/name&gt;
  &lt;uuid&gt;51480ab5-864e-4eb7-9e1c-55b56105139e&lt;/uuid&gt;
  &lt;memory unit='KiB'&gt;524288&lt;/memory&gt;
  &lt;currentMemory unit='KiB'&gt;524288&lt;/currentMemory&gt;
  &lt;vcpu placement='static'&gt;1&lt;/vcpu&gt;
  &lt;os&gt;
    &lt;type arch='x86_64' machine='pc'&gt;hvm&lt;/type&gt;
  &lt;/os&gt;
  &lt;features&gt;
    &lt;acpi/&gt;
  &lt;/features&gt;
  &lt;cpu mode='custom' match='exact'&gt;
    &lt;model fallback='allow'&gt;Haswell&lt;/model&gt;
  &lt;/cpu&gt;
  &lt;clock offset='utc'/&gt;
  &lt;on_poweroff&gt;destroy&lt;/on_poweroff&gt;
  &lt;on_reboot&gt;restart&lt;/on_reboot&gt;
  &lt;on_crash&gt;destroy&lt;/on_crash&gt;
  &lt;devices&gt;
    &lt;emulator&gt;/usr/bin/qemu-system-x86_64&lt;/emulator&gt;
    &lt;disk type='file' device='disk'&gt;
      &lt;driver name='qemu' type='raw'/&gt;
      &lt;source file='/home/alex/Vmware/KVM/fedora-10/hda.img'/&gt;
      &lt;target dev='vda' bus='virtio'/&gt;
    &lt;/disk&gt;
    &lt;controller type='usb' index='0'/&gt;
    &lt;controller type='pci' index='0' model='pci-root'/&gt;
    &lt;input type='mouse' bus='ps2'/&gt;
    &lt;input type='keyboard' bus='ps2'/&gt;
    &lt;graphics type='sdl'/&gt;
    &lt;video&gt;
      &lt;model type='cirrus' vram='9216' heads='1'/&gt;
    &lt;/video&gt;
    &lt;memballoon model='virtio'/&gt;
  &lt;/devices&gt;
  &lt;qemu:commandline&gt;
    &lt;qemu:arg value='-daemonize'/&gt;
    &lt;qemu:arg value='-display'/&gt;
    &lt;qemu:arg value='none'/&gt;
    &lt;qemu:arg value='-netdev'/&gt;
    &lt;qemu:arg value='bridge,id=tap0,br=br0'/&gt;
    &lt;qemu:arg value='-device'/&gt;
    &lt;qemu:arg value='virtio-net-pci,netdev=tap0,mac=DE:AD:BE:EF:F1:00'/&gt;
  &lt;/qemu:commandline&gt;
&lt;/domain&gt;</pre>
<p>可以看到，很多QEMU选项没有对应到常规的Domain配置元素，而是使用qemu:commandline的形式，在启动客户机的时候直接传递给QEMU了。因此，新建客户机时，不要使用这种导入配置的方法，而应调用libvirt API或者手工创建Domain的XML配置。</p>
<p>注意：virsh自动导入得到XML配置存在不少错误，需要调整后才能使用。上例修改后的配置如下：</p>
<pre class="crayon-plain-tag">&lt;domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'&gt;
    &lt;name&gt;fedora-10&lt;/name&gt;
    &lt;memory unit='KiB'&gt;524288&lt;/memory&gt;
    &lt;currentMemory unit='KiB'&gt;524288&lt;/currentMemory&gt;
    &lt;vcpu placement='static'&gt;1&lt;/vcpu&gt;
    &lt;os&gt;
        &lt;type arch='x86_64' machine='pc'&gt;hvm&lt;/type&gt;
    &lt;/os&gt;
    &lt;features&gt;
        &lt;acpi/&gt;
    &lt;/features&gt;
    &lt;cpu mode='custom' match='exact'&gt;
        &lt;model fallback='allow'&gt;SandyBridge&lt;/model&gt;
    &lt;/cpu&gt;
    &lt;clock offset='utc'/&gt;
    &lt;on_poweroff&gt;destroy&lt;/on_poweroff&gt;
    &lt;on_reboot&gt;restart&lt;/on_reboot&gt;
    &lt;on_crash&gt;destroy&lt;/on_crash&gt;
    &lt;devices&gt;
        &lt;emulator&gt;/usr/bin/qemu-system-x86_64&lt;/emulator&gt;
        &lt;disk type='file' device='disk'&gt;
            &lt;driver name='qemu' type='qcow2'/&gt; &lt;!-- 镜像格式要设置对 --&gt;
            &lt;source file='/home/alex/Vmware/KVM/fedora-10/hda.img'/&gt;
            &lt;target dev='vda' bus='virtio'/&gt;
            &lt;boot order='1'/&gt;  &lt;!-- 要指定启动顺序，否则不会从此硬盘启动系统 --&gt;
        &lt;/disk&gt;
        &lt;controller type='usb' index='0'/&gt;
        &lt;controller type='pci' index='0' model='pci-root'/&gt;
        &lt;!-- 把基于qemu:commandline的网络配置改为标准的Domain配置方式 --&gt;
        &lt;interface type='bridge'&gt;
            &lt;mac address='DE:AD:BE:EF:F1:00'/&gt;
            &lt;source bridge='br0'/&gt;
            &lt;target dev='tap0'/&gt;
            &lt;model type='virtio'/&gt;
        &lt;/interface&gt; 
        &lt;!-- 添加串口控制台配置，去掉视频相关配置 --&gt;
        &lt;serial type='pty'&gt;
            &lt;target port='0'/&gt;
        &lt;/serial&gt;
        &lt;console type='pty'&gt;
            &lt;target type='serial' port='0'/&gt;
        &lt;/console&gt;
        &lt;input type='mouse' bus='ps2'/&gt;
        &lt;input type='keyboard' bus='ps2'/&gt;
        &lt;memballoon model='virtio'/&gt;
    &lt;/devices&gt;
&lt;/domain&gt; </pre>
<div class="blog_h3"><span class="graybg">导出</span></div>
<p>类似的，可以把Domain配置文件转换为QEMU命令行：</p>
<pre class="crayon-plain-tag">virsh domxml-to-native qemu-argv ~/Vmware/KVM/fedora-10/domain.xml</pre>
<div class="blog_h1"><span class="graybg">Domain配置文件</span></div>
<div class="blog_h2"><span class="graybg">基本信息</span></div>
<p>libvirt使用XML文件描述一个Domain的全部配置信息：</p>
<pre class="crayon-plain-tag">&lt;!--
任何Domain配置的根元素都是domin，它有两个属性：
    type 驱动（hypervisor）的类型，可选值：xen | kvm | qemu | lxc | kqemu
    id 正在运行的Domain的唯一标识，整数。非活动Domain没有id
--&gt;
&lt;domain type='kvm' id='1'&gt;
    &lt;!-- 在单个node下唯一的虚拟机名字 --&gt;
    &lt;name&gt;fedora-10&lt;/name&gt;
    &lt;!-- RFC 4122兼容的、虚拟机的全局唯一标识，如果在定义/创建虚拟机时不指定，则会自动生成一个  --&gt;
    &lt;uuid&gt;4dea22b31d52d8f32516782e98ab3fa0&lt;/uuid&gt;
    &lt;!-- 虚拟机的简短描述，可以多行 --&gt;
    &lt;title&gt;A short description&lt;/title&gt;
    &lt;!-- 虚拟机的描述--&gt;
    &lt;description&gt;Some human readable description&lt;/description&gt;
    &lt;!-- 元数据：由应用程序使用，子树必须使用应用程序自己的名字空间 --&gt;
    &lt;metadata&gt;
        &lt;app1:foo xmlns:app1="http://app1.org/app1/"&gt;..&lt;/app1:foo&gt;
        &lt;app2:bar xmlns:app2="http://app1.org/app2/"&gt;..&lt;/app2:bar&gt;
    &lt;/metadata&gt;
&lt;/domain&gt;</pre>
<p>下面介绍如何配置Domain各方面的细节。</p>
<div class="blog_h2"><span class="graybg">Domain启动配置</span></div>
<p>虚拟机可以不同的方式启动，各有其优缺点。</p>
<div class="blog_h3"><span class="graybg">BIOS bootloader</span></div>
<p>对于全虚拟化的hypervisor可以选择通过BIOS启动，BIOS定义启动优先级，来确定从软盘、硬盘、光驱还是网络获取启动镜像（boot image）。配置示例：</p>
<pre class="crayon-plain-tag">&lt;os&gt;
    &lt;type&gt;hvm&lt;/type&gt;
    &lt;loader readonly='yes' secure='no' type='rom'&gt;/usr/lib/xen/boot/hvmloader&lt;/loader&gt;
    &lt;nvram template='/usr/share/OVMF/OVMF_VARS.fd'&gt;/var/lib/libvirt/nvram/guest_VARS.fd&lt;/nvram&gt;
    &lt;boot dev='hd'/&gt;
    &lt;boot dev='cdrom'/&gt;
    &lt;bootmenu enable='yes' timeout='3000'/&gt;
    &lt;smbios mode='sysinfo'/&gt;
    &lt;bios useserial='yes' rebootTimeout='0'/&gt;
&lt;/os&gt;</pre>
<p>各子元素的说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>type</td>
<td>
<p>指定需要被启动的虚拟机操作系统的类型：<br />hvm    操作系统设计用来在裸金属（bare metal）上运行，即未修改的操作系统，需要全虚拟化<br />xen     表示支持Xen3的Guest API的操作系统，别名linux<br />exe    基于容器的虚拟化<br />uml   用户模式的Linux</p>
<p>元素属性：<br />arch    指定被虚拟化的CPU的体系结构<br />machine    指定机器类型</p>
</td>
</tr>
<tr>
<td>loader</td>
<td>
<p>指定虚拟机固件镜像的（宿主机的）绝对路径。用于Xen全虚拟化、QEMU/KVM的BIOS文件路径设置</p>
<p>元素属性：<br />readonly    固件是否只读，yes/no<br />type    rom/pflash，告知hypervisor把固件映射到客户机内存的什么位置，如果loader指定UEFI镜像，则该属性应为pflash<br />secure    指示固件实现了安全启动（secure boot）特性</p>
</td>
</tr>
<tr>
<td>boot</td>
<td>
<p>此元素可以出现多次，其dev属性可以是fd/hd/cdrom/network，用来确定优先从哪种设备启动虚拟机，写在最前面的那种设备优先级高</p>
<p>如果同一类型的设备配置了多个，它们将依据总线顺序排列，第一个被标记为可启动的。该元素难以细粒度的控制启动顺序，可以使用Per-device的boot元素代替（后者与此元素互斥）</p>
</td>
</tr>
<tr>
<td>smbios</td>
<td>产生客户机可见的SMBios信息，引用一个sysinfo元素</td>
</tr>
<tr>
<td>bootmenu</td>
<td>可以使用一个交互式的启动菜单</td>
</tr>
<tr>
<td>bios</td>
<td>useserial：可以设置为yes/no，来启用/禁用Serial Graphics Adapter ，SGA允许用户通过串口看到BIOS信息<br />rebootTimeout   如果启动失败，多久重启，单位毫秒，-1禁止重启</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Container boot<br /></span></div>
<p>当启动基于容器虚拟化的Domain时，需要指定一个init程序：</p>
<pre class="crayon-plain-tag">&lt;os&gt;
    &lt;type arch='x86_64'&gt;exe&lt;/type&gt;
    &lt;!-- init binary --&gt;
    &lt;init&gt;/bin/systemd&lt;/init&gt;
    &lt;!-- argumsnts --&gt;
    &lt;initarg&gt;--unit&lt;/initarg&gt;
    &lt;initarg&gt;emergency.service&lt;/initarg&gt;
&lt;/os&gt;</pre>
<p>如果你要启用user namespace映射，可以：</p>
<pre class="crayon-plain-tag">&lt;idmap&gt;
    &lt;!-- start容器内第1个用户的ID，target容器内第1个用户映射到宿主机的用户的ID，count容器最多映射宿主机多少用户--&gt;
    &lt;uid start='0' target='1000' count='10'/&gt;
    &lt;gid start='0' target='1000' count='10'/&gt;
&lt;/idmap&gt;</pre>
<div class="blog_h2"><span class="graybg">指定SMBIOS信息</span></div>
<pre class="crayon-plain-tag">&lt;os&gt;
    &lt;smbios mode='sysinfo'/&gt;
&lt;/os&gt;
&lt;!-- type属性必须，它的值决定子元素第布局--&gt;
&lt;sysinfo type='smbios'&gt;
    &lt;bios&gt;
        &lt;entry name='vendor'&gt;LENOVO&lt;/entry&gt;
    &lt;/bios&gt;
    &lt;system&gt;
        &lt;entry name='manufacturer'&gt;Fedora&lt;/entry&gt;
        &lt;entry name='product'&gt;Virt-Manager&lt;/entry&gt;
        &lt;entry name='version'&gt;0.9.4&lt;/entry&gt;
    &lt;/system&gt;
    &lt;baseBoard&gt;
        &lt;entry name='manufacturer'&gt;LENOVO&lt;/entry&gt;
        &lt;entry name='product'&gt;20BE0061MC&lt;/entry&gt;
        &lt;entry name='version'&gt;0B98401 Pro&lt;/entry&gt;
        &lt;entry name='serial'&gt;W1KS427111E&lt;/entry&gt;
    &lt;/baseBoard&gt;
&lt;/sysinfo&gt;</pre>
<div class="blog_h2"><span class="graybg">CPU分配</span></div>
<p>配置示例如下：</p>
<pre class="crayon-plain-tag">&lt;vcpu placement='static' cpuset="1-4,^3,6" current="1"&gt;2&lt;/vcpu&gt;
&lt;vcpus&gt;
    &lt;vcpu id='0' enabled='yes' hotpluggable='no' order='1'/&gt;
    &lt;vcpu id='1' enabled='no' hotpluggable='yes'/&gt;
&lt;/vcpus&gt;</pre>
<div class="blog_h3"><span class="graybg">vcpu元素</span></div>
<p>此元素定义客户机<span style="background-color: #c0c0c0;">最大的虚拟CPU的数量</span>，有效值的范围是1-hypervisor支持的最大数量。属性说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>cpuset</td>
<td>
<p>逗号分隔的，Domain进程及虚拟CPU默认能够Pin到的物理CPU序号。可以用-指定范围，^进行排除</p>
<p>Domain进程及虚拟CPU的Pin策略可以由cputune指定，如果cputune的emulatorpin属性被设置，则当前属性被忽略<br />对于指定了vcpupin的虚拟CPU，当前属性被忽略</p>
</td>
</tr>
<tr>
<td>current</td>
<td>
<p>启用比最大数量更少的虚拟CPU</p>
</td>
</tr>
<tr>
<td>placement</td>
<td>
<p>指定Domain进程的CPU placement mode，static/auto</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">vcpus</span></div>
<p>此元素控制每个单独虚拟CPU的状态，每个vcpu子元素对应一个虚拟CPU，vcpu子元素的属性说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>虚拟CPU的标识符，libvirt在其它地方（例如pinning）引用之。有效值范围0到最大虚拟CPU数-1之间</td>
</tr>
<tr>
<td>enabled</td>
<td>控制此虚拟CPU是否启用，yes/no</td>
</tr>
<tr>
<td>hotpluggable</td>
<td>此虚拟CPU是否可以热拔插，注意，所有enabled=no的CPU都是可以热拔插的，yes/no</td>
</tr>
<tr>
<td>order</td>
<td>此虚拟CPU的顺序号，此值越小，则CPU越先被热插</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">CPU微调</span></div>
<p>cputune元素可以对Domain的虚拟CPU进行微调，配置示例如下：</p>
<pre class="crayon-plain-tag">&lt;cputune&gt;
    &lt;vcpupin vcpu="0" cpuset="1-4,^2"/&gt;
    &lt;vcpupin vcpu="1" cpuset="0,1"/&gt;
    &lt;vcpupin vcpu="2" cpuset="2,3"/&gt;
    &lt;vcpupin vcpu="3" cpuset="0,4"/&gt;
    &lt;emulatorpin cpuset="1-3"/&gt;
    &lt;iothreadpin iothread="1" cpuset="5,6"/&gt;
    &lt;iothreadpin iothread="2" cpuset="7,8"/&gt;
    &lt;shares&gt;2048&lt;/shares&gt;
    &lt;period&gt;1000000&lt;/period&gt;
    &lt;quota&gt;-1&lt;/quota&gt;
    &lt;emulator_period&gt;1000000&lt;/emulator_period&gt;
    &lt;emulator_quota&gt;-1&lt;/emulator_quota&gt;
    &lt;iothread_period&gt;1000000&lt;/iothread_period&gt;
    &lt;iothread_quota&gt;-1&lt;/iothread_quota&gt;
    &lt;vcpusched vcpus='0-4,^3' scheduler='fifo' priority='1'/&gt;
    &lt;iothreadsched iothreads='2' scheduler='batch'/&gt;
&lt;/cputune&gt;</pre>
<p>各子元素说明如下：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>vcpupin</td>
<td>指定虚拟CPU与物理CPU之间的Pin关系。不指定的情况下，虚拟CPU可以Pin到所有物理CPU</td>
</tr>
<tr>
<td>emulatorpin</td>
<td>模拟器线程被Pin到哪些物理CPU，模拟器线程是Domain执行序列中除了虚拟CPU、IO线程之外的部分<br />如果此属性未指定，且vcpu的cpuset属性没有设置，则默认Pin到所有物理CPU</td>
</tr>
<tr>
<td>iothreadpin</td>
<td>IO线程被Pin到哪些物理CPU<br />如果此属性未指定，且vcpu的cpuset属性没有设置，则默认Pin到所有物理CPU</td>
</tr>
<tr>
<td>shares</td>
<td>此Domain占用CPU时间的权重，这是一个相对值，它对应的具体时长取决于其它虚拟机的设置<br />2048比1024多获得1倍CPU时间</td>
</tr>
<tr>
<td>period</td>
<td>
<p>用来指定强制的interval，单位毫秒，可取值 [1000, 1000000]之间。在一个period内，Domain中的每个虚拟CPU消耗的带宽不得超过quota</p>
<p>仅QEMU 0.9.4+、LXC 0.9.10+支持</p>
</td>
</tr>
<tr>
<td>quota</td>
<td>用来指定最大允许带宽，单位毫秒，可取值 [1000, 18446744073709551]，负值表示不限制</td>
</tr>
<tr>
<td>emulator_period</td>
<td rowspan="2">与上面类似，但是针对模拟器线程</td>
</tr>
<tr>
<td>emulator_quota</td>
</tr>
<tr>
<td>iothread_period</td>
<td rowspan="2">与上面类似，但是针对IO线程</td>
</tr>
<tr>
<td>iothread_quota</td>
</tr>
<tr>
<td>vcpusched</td>
<td>指定特定虚拟CPU的调度类型：<br />scheduler，调度类型，可选batch, idle, fifo, rr<br />vcpus，针对的虚拟CPU<br />priority，对于实时调度器fifo, rr必须，值范围一般1-99之间，取决于宿主机内核</td>
</tr>
<tr>
<td>iothreadsched</td>
<td>与上面类似</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">IO线程分配</span></div>
<p>IO线程是一种专门的事件循环线程，用于提高磁盘Block I/O的scalability，这些线程会分配给支持的磁盘设备。每个物理CPU只有1-2个IO线程，每个IO线程也可能分配给多个磁盘设备。配置示例：</p>
<pre class="crayon-plain-tag">&lt;!-- 分配给Domain使用的IO线程数--&gt;
&lt;iothreads&gt;4&lt;/iothreads&gt;

&lt;!-- 定义每个IO线程的ID --&gt;
&lt;iothreadids&gt;
    &lt;iothread id="2"/&gt;
    &lt;iothread id="4"/&gt;
    &lt;iothread id="6"/&gt;
    &lt;iothread id="8"/&gt;
&lt;/iothreadids&gt;</pre>
<div class="blog_h2"><span class="graybg">内存分配</span></div>
<p>配置示例如下：</p>
<pre class="crayon-plain-tag">&lt;maxMemory slots='16' unit='KiB'&gt;1524288&lt;/maxMemory&gt;
&lt;memory unit='KiB'&gt;524288&lt;/memory&gt;
&lt;currentMemory unit='KiB'&gt;524288&lt;/currentMemory&gt;</pre>
<p>各元素说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>memory</td>
<td>在启动时，分配给Domain的内存的数量<br />unit，单位，Ki按1024，K按1000计，可用B|KB|MB|GB|TB<br />dumpCore，在Domain崩溃后，是否包含其内存映像到生成的coredump中，仅QEMU</td>
</tr>
<tr>
<td>maxMemory</td>
<td>运行期间允许的最大内存，仅QEMU</td>
</tr>
<tr>
<td>currentMemory</td>
<td>当前实际分配给Domain的内存，默认与memory相同</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">内存Backing</span></div>
<p>memoryBacking元素控制虚拟内存页如何映射到宿主机的内存页，配置示例：</p>
<pre class="crayon-plain-tag">&lt;memoryBacking&gt;
    &lt;hugepages&gt;
        &lt;!-- 为了除4之外的numa节点分配1G的巨页 --&gt;
        &lt;page size="1" unit="G" nodeset="0-3,5"/&gt;
        &lt;page size="2" unit="M" nodeset="4"/&gt;
    &lt;/hugepages&gt;
    &lt;nosharepages/&gt;
    &lt;locked/&gt;
&lt;/memoryBacking&gt;</pre>
<p>子元素说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>hugepages</td>
<td>
<p>在Linux 64位系统里面，默认内存是以<span style="background-color: #c0c0c0;">4K的页面（Page）</span>来管理的，当系统有非常多的内存的时候，管理这些内存的消耗就比较大。HugePage使用<span style="background-color: #c0c0c0;">2M大小的页面来减小管理开销</span>。<span style="background-color: #c0c0c0;">HugePage管理的内存并不能被Swap</span>，这就避免了swap引发的性能问题。如果系统经常碰到因为swap引发的性能问题可以考虑启用HugePage</p>
<p>告知hypervisor，客户机的内存基于hugepage而不是宿主机Native页大小来分配。从1.2.5开始，可以为每个numa节点更加细致的设置huagepages：<br />size/unit    指定huge页的大小<br />nodeset    给于特定numa节点hugepage</p>
</td>
</tr>
<tr>
<td>nosharepages</td>
<td>nosharepages，用于提示hypervisor禁止此Domain的共享页面（内存合并，KSM）</td>
</tr>
<tr>
<td>locked</td>
<td>
<p>如果hypervisor支持，设置此元素可以禁止属于Domain的内存页被swap out</p>
<p>对于QEMU/KVM，使用此设置前你需要设置memtune的hard_limit，并且设置maxMemory=Domain所需内存+QEMU进程本身所需内存。注意：启用locked且设置过多的内存可能导致宿主机内核内存溢出</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">内存微调</span></div>
<p>memtune提供Domain的内存微调参数，如果不设置这些参数，则使用OS提供的默认值。对于QEMU/KVM，这些参数限制包含QEMU进程本身的内存消耗</p>
<pre class="crayon-plain-tag">&lt;memtune&gt;
    &lt;hard_limit unit='G'&gt;1&lt;/hard_limit&gt;
    &lt;soft_limit unit='M'&gt;128&lt;/soft_limit&gt;
    &lt;swap_hard_limit unit='G'&gt;2&lt;/swap_hard_limit&gt;
    &lt;min_guarantee unit='bytes'&gt;67108864&lt;/min_guarantee&gt;
&lt;/memtune&gt;</pre>
<p>子元素说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>hard_limit</td>
<td>限制客户机能够使用的最大内存，对于QEMU/KVM建议不要设置</td>
</tr>
<tr>
<td>soft_limit</td>
<td>出现内存争用时的软限制</td>
</tr>
<tr>
<td>swap_hard_limit</td>
<td>最大内存 + 交换文件总大小限制</td>
</tr>
<tr>
<td>min_guarantee</td>
<td>确保最小低分配给客户机的内存，仅VMware ESX、OpenVZ支持</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">NUMA节点微调</span></div>
<p>numatune元素通过控制针对Domain进程的numa策略来影响宿主机的性能，配置示例如下：</p>
<pre class="crayon-plain-tag">&lt;numatune&gt;
    &lt;memory mode="strict" nodeset="1-4,^3"/&gt;
    &lt;memnode cellid="0" mode="strict" nodeset="1"/&gt;
    &lt;memnode cellid="2" mode="preferred" nodeset="2"/&gt;
&lt;/numatune&gt;</pre>
<p>子元素说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>memory</td>
<td>如何在numa主机上为Domain分配内存：<br />mode，可选值interleave,strict,preferred，默认strict<br />nodeset，影响的numa节点</td>
</tr>
<tr>
<td>memnode</td>
<td>类似，针对单个numa节点设置</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">块I/O微调</span></div>
<p>blkiotune元素能够微调Domain的Blkio cgroup可调整参数，如果不指定此元素，则使用OS默认值。配置示例如下：</p>
<pre class="crayon-plain-tag">&lt;blkiotune&gt;
    &lt;weight&gt;800&lt;/weight&gt;
    &lt;device&gt;
        &lt;path&gt;/dev/sda&lt;/path&gt;
        &lt;weight&gt;1000&lt;/weight&gt;
    &lt;/device&gt;
    &lt;device&gt;
        &lt;path&gt;/dev/sdb&lt;/path&gt;
        &lt;weight&gt;500&lt;/weight&gt;
        &lt;read_bytes_sec&gt;10000&lt;/read_bytes_sec&gt;
        &lt;write_bytes_sec&gt;10000&lt;/write_bytes_sec&gt;
        &lt;read_iops_sec&gt;20000&lt;/read_iops_sec&gt;
        &lt;write_iops_sec&gt;20000&lt;/write_iops_sec&gt;
    &lt;/device&gt;
&lt;/blkiotune&gt;</pre>
<p>子元素说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>weight</td>
<td>Domain的整体I/O权重，值范围[100, 1000]，自2.6.39内核之后，值范围[10, 1000]</td>
</tr>
<tr>
<td>device</td>
<td>
<p>此元素可以有多个，用来设置Domain针对宿主机每一个块设备的I/O权重。子元素：<br />path    宿主机块设备文件的路径<br />weight    针对此块设备，Domain的权重<br />read_bytes_sec    读吞吐量限制，bytes/s<br />write_bytes_sec    写吞吐量限制，bytes/s<br />read_iops_sec    读次数限制，bytes/s<br />write_iops_sec    写次数限制，bytes/s</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">CPU型号与拓扑</span></div>
<p>对CPU型号、特性的要求，以及它的拓扑结构的要求，可以使用如下方式配置：</p>
<pre class="crayon-plain-tag">&lt;cpu match='exact'&gt;
    &lt;model fallback='allow'&gt;core2duo&lt;/model&gt;
    &lt;vendor&gt;Intel&lt;/vendor&gt;
    &lt;topology sockets='1' cores='2' threads='1'/&gt;
    &lt;feature policy='disable' name='lahf_lm'/&gt;
&lt;/cpu&gt;

&lt;cpu mode='host-model'&gt;
    &lt;model fallback='forbid'/&gt;
    &lt;topology sockets='1' cores='2' threads='1'/&gt;
&lt;/cpu&gt;

&lt;cpu mode='host-passthrough'&gt;
    &lt;feature policy='disable' name='lahf_lm'/&gt;
&lt;/cpu&gt;</pre>
<p>cpu元素是描述客户机CPU需求的容器元素，它的属性如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>match</td>
<td>宿主机必须满足客户机CPU需求的严格程度，可选值：<br />minimum    满足CPU型号、feature的要求<br />exact    完全满足<br />strict    除非完全满足，否则客户机不被创建</td>
</tr>
<tr>
<td>mode<a id="cpu-mode"></a></td>
<td>
<p>用于简化客户机的配置，让它尽量和宿主机CPU匹配。可选值：</p>
<ol style="list-style-type: undefined;">
<li>custom（94.73%性能）：这种模式下虚拟机 CPU 指令集数最少，故性能相对最差，但是它在热迁移时跨不同型号 CPU 的能力最强。此外，custom 模式下支持用户添加额外的指令集</li>
<li>host-model（95.84%性能）：libvirt 根据当前宿主机 CPU 指令集从配置文件 /usr/share/libvirt/cpu_map.xml 选择一种最相配的 CPU 型号。在这种 mode 下，虚拟机的指令集往往比宿主机少，性能相对 host-passthrough 要差一点，但是热迁移时，它允许目的节点 CPU 和源节点的存在一定的差异</li>
<li>host-passthrough(100%性能）：libvirt 令 KVM 把宿主机的 CPU 指令集全部透传给虚拟机。因此虚拟机能够最大限度的使用宿主机 CPU 指令集，故性能是最好的。但是在热迁移时，它要求目的节点的 CPU 和源节点的一致</li>
</ol>
<p>关于热迁移，理论上来说：</p>
<ol style="list-style-type: undefined;">
<li>host-passthrough: 要求源节点和目的节点的指令集完全一致</li>
<li>host-model: 允许源节点和目的节点的指令集存在轻微差异</li>
<li>custom: 允许源节点和目的节点指令集存在较大差异</li>
</ol>
<p>从实际情况来看，公司不同时间采购的 CPU 型号可能不相同；不同业务对 CPU 型号的要求也有差异。虽然互联网多采用 intel E5 系列的 CPU，但是该系列的 CPU 也有多种型号，常见的有 Xeon，Haswell，IvyBridge，SandyBridge 等等。即使是 host-model，在这些不同型号的 CPU 之间热迁移虚拟机也可能失败。所以从热迁移的角度，在选择 host-mode 时：</p>
<ol>
<li>需要充分考虑既有宿主机类型，以后采购扩容时，也需要考虑相同问题</li>
<li>除非不存在热迁移的场景，否则不应用选择 host-passthrough</li>
<li>host-model 下不同型号的 CPU 最好能以 aggregate hosts 划分，在迁移时可以使用 aggregate filter 来匹配相同型号的物理机</li>
<li>如果 CPU 型号过多，且不便用 aggregate hosts 划分，建议使用 custom mode</li>
</ol>
</td>
</tr>
</tbody>
</table>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>model</td>
<td>
<p>指定客户机要求的CPU型号，可用型号的列表在/usr/share/libvirt/cpu_map.xml<br />如果hypervisor不能使用精确的CPU，libvirt会自动fallback到特性最接近的CPU</p>
<p>属性列表：<br />fallback，是否允许fallback，可选值allow,forbid，默认allow<br />vendor_id，设置客户机看到的vendor_id，必须12字符长，典型值AuthenticAMD、GenuineIntel</p>
</td>
</tr>
<tr>
<td>vendor</td>
<td>设置客户机要求CPU的厂商，可用厂商列在cpu_map.xml</td>
</tr>
<tr>
<td>topology</td>
<td>规定总的CPU插槽数，每个CPU的核心数，每个核心的硬件线程数</td>
</tr>
<tr>
<td>feature</td>
<td>可以包含多个这样的元素，用来细粒度的规定CPU的特性：<br />name    特性名称<br />policy    策略：force强制要求此特性，即使宿主机不支持；require如果宿主机支持则要求此特性；optional可有可无；disable此特性在客户机上禁用；forbid如果宿主机支持此特性则客户机失败</td>
</tr>
<tr>
<td>numa</td>
<td>
<p>仅适用于QEMU/KVM，指定客户机的numa拓扑，举例：</p>
<pre class="crayon-plain-tag">&lt;numa&gt;
    &lt;cell id='0' cpus='0-3' memory='512000' unit='KiB'/&gt;
    &lt;cell id='1' cpus='4-7' memory='512000' unit='KiB' memAccess='shared'/&gt;
&lt;/numa&gt;</pre>
<p> 每个cell子元素对应一个numa 节点（cell）：<br />cpus指定节点对应的CPU范围<br />memory指定节点本地内存大小<br />memAccess控制内存被映射为shared还是private，此属性仅针对基于hugepage的内存</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">事件配置</span></div>
<p>你可能需要覆盖某些事件发生时采取的动作，注意并非所有hypervisors支持所有事件和动作。使用<pre class="crayon-plain-tag">virsh reboot</pre> 或者<pre class="crayon-plain-tag">virsh shutdown</pre> 可以触发事件。配置示例：</p>
<pre class="crayon-plain-tag">&lt;on_poweroff&gt;destroy&lt;/on_poweroff&gt;
&lt;on_reboot&gt;restart&lt;/on_reboot&gt;
&lt;on_crash&gt;restart&lt;/on_crash&gt;
&lt;on_lockfailure&gt;poweroff&lt;/on_lockfailure&gt;</pre>
<p> 事件类型采用元素表示：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>on_poweroff</td>
<td>指定客户机请求断开电源时采取的动作</td>
</tr>
<tr>
<td>on_reboot</td>
<td>指定客户机请求重启时采取的动作</td>
</tr>
<tr>
<td>on_crash</td>
<td>指定客户机崩溃时采取的动作</td>
</tr>
</tbody>
</table>
<p>这几种事件都支持的动作包括：</p>
<ol>
<li>destroy，终止Domain并释放一切相关资源</li>
<li>restart，Domain被终止，并以相同的配置再次启动</li>
<li>preserve，Domain被终止但是其资源被保留供分析</li>
<li>rename-restart，以另外一个名字重启Domain</li>
</ol>
<p>on_crash支持额外的动作：</p>
<ol>
<li>coredump-destroy，崩溃Domain的core被dump出来，然后destroy</li>
<li>coredump-restart，崩溃Domain的core被dump出来，然后重启</li>
</ol>
<div class="blog_h2"><span class="graybg">电源管理</span></div>
<p>仅QEMU支持，强制启用/禁止客户机BIOS的电源管理功能：</p>
<pre class="crayon-plain-tag">&lt;pm&gt;
    &lt;!-- 是否启用ACPI的S4睡眠状态 --&gt;
    &lt;suspend-to-disk enabled='no'/&gt;
    &lt;!-- 是否启用ACPI的S3睡眠状态 --&gt;
    &lt;suspend-to-mem enabled='yes'/&gt;
&lt;/pm&gt;</pre>
<div class="blog_h2"><span class="graybg">Hypervisor特性</span></div>
<p>Hypervisor能够启用/禁用一些CPU/机器特性。配置示例：</p>
<pre class="crayon-plain-tag">&lt;features&gt;
    &lt;pae/&gt; &lt;!-- 物理地址扩展，允许32位客户机访问超过4GB内存 --&gt;
    &lt;acpi/&gt; &lt;!-- 对于电源管理有用，例如可以优雅的关闭KVM客户机 --&gt;
    &lt;apic/&gt; &lt;!-- 支持可编程的中断请求管理 --&gt;
    &lt;hap/&gt;  &lt;!-- 根据state=on|off，启禁硬件辅助paging --&gt;
    &lt;privnet/&gt; &lt;!-- 总是创建私有的网络命名空间，如果任何网络接口设备被添加，则自动设置。仅和容器虚拟化相关 --&gt;
    &lt;hyperv&gt; &lt;!-- 调整Windows客户机的性能，仅QEMU2.x --&gt;
        &lt;relaxed state='on'/&gt;
        &lt;vapic state='on'/&gt;
        &lt;spinlocks state='on' retries='4096'/&gt;
        &lt;vpindex state='on'/&gt;
        &lt;runtime state='on'/&gt;
        &lt;synic state='on'/&gt;
        &lt;reset state='on'/&gt;
        &lt;vendor_id state='on' value='KVM Hv'/&gt;
    &lt;/hyperv&gt;
    &lt;kvm&gt;
        &lt;hidden state='on'/&gt; &lt;!-- 从标准的MSR发现中隐藏KVM的Hypervisor --&gt;
    &lt;/kvm&gt;
    &lt;pvspinlock state='on'/&gt;  &lt;!-- 通知客户机，宿主机支持半虚拟化的自旋锁 --&gt;
&lt;/features&gt;</pre>
<div class="blog_h2"><span class="graybg">时间保持</span></div>
<p>客户机的时间通常是基于宿主机时间来初始化的，大部分OS期望硬件中存储的是UTC时间，然而Windows期望的则是“本地时间”。</p>
<p>配置示例：</p>
<pre class="crayon-plain-tag">&lt;clock offset='localtime'&gt;
    &lt;timer name='rtc' tickpolicy='catchup' track='guest'&gt;
        &lt;catchup threshold='123' slew='120' limit='10000'/&gt;
    &lt;/timer&gt;
    &lt;timer name='pit' tickpolicy='delay'/&gt;
&lt;/clock&gt;</pre>
<p>clock的offset属性控制客户机的时间如何与宿主机同步：</p>
<ol>
<li>utc，客户机启动时总是基于UTC时间来同步</li>
<li>localtime，客户机启动时基于宿主机的timezone配置来同步时间</li>
<li>timezone，客户机基于指定的时区来同步</li>
<li>variable，客户机的时钟相对于UTC或者localtime（由basis属性指定，默认utc）具有一定的偏移，偏移量单位秒，由adjustment指定</li>
</ol>
<div class="blog_h2"><span class="graybg">设备配置</span></div>
<p>提供给客户机的所有设备，都在<pre class="crayon-plain-tag">&lt;devices&gt;</pre> 元素中配置。本章后续内容讲述各种设备的XML配置信息。</p>
<div class="blog_h2"><span class="graybg">模拟器路径</span></div>
<p>可以使用下面的元素来指定模拟器全限定的路径：</p>
<pre class="crayon-plain-tag">&lt;devices&gt;
    &lt;emulator&gt;/usr/lib/xen/bin/qemu-dm&lt;/emulator&gt;
&lt;/devices&gt;</pre>
<p>capabilities的XML配置指明了特定Domain类型-体系结构组合对应的最佳模拟器。</p>
<div class="blog_h2"><span class="graybg">软/硬/光盘</span></div>
<p>任何软盘、硬盘、光盘或者半虚拟化的驱动器，都是通过<pre class="crayon-plain-tag">disk</pre> 元素来指定的。配置示例：</p>
<pre class="crayon-plain-tag">&lt;devices&gt;
    &lt;!-- 后端支持是文件的磁盘，使用外部快照 --&gt;
    &lt;disk type='file' snapshot='external'&gt;
        &lt;!-- xen的驱动微调 --&gt;
        &lt;driver name="tap" type="aio" cache="default"/&gt;
        &lt;!-- 磁盘的源——文件的位置 --&gt;
        &lt;source file='/var/lib/xen/images/fv0' startupPolicy='optional'&gt;
            &lt;seclabel relabel='no'/&gt;
        &lt;/source&gt;
        &lt;!-- 磁盘在客户机中的期望设备名，总线类型 --&gt;
        &lt;target dev='hda' bus='ide'/&gt;
        &lt;!-- IO节流阀 --&gt;
        &lt;iotune&gt;
            &lt;total_bytes_sec&gt;10000000&lt;/total_bytes_sec&gt;
            &lt;read_iops_sec&gt;400000&lt;/read_iops_sec&gt;
            &lt;write_iops_sec&gt;100000&lt;/write_iops_sec&gt;
        &lt;/iotune&gt;
        &lt;!-- 该磁盘可以作为启动盘，顺序2 --&gt;
        &lt;boot order='2'/&gt;
        &lt;!-- 启用磁盘加密 --&gt;
        &lt;encryption type='...'&gt;
            ...
        &lt;/encryption&gt;
        &lt;shareable/&gt;&lt;!-- 磁盘可以被多个Domain共享--&gt;
        &lt;serial&gt;...&lt;/serial&gt;&lt;!-- 指定磁盘序列号 --&gt;
    &lt;/disk&gt;
    &lt;!-- 该磁盘的源位于网络上 --&gt;
    &lt;disk type='network'&gt;
        &lt;!-- QEMU驱动微调，指定AIO基于pthread --&gt;
        &lt;driver name="qemu" type="raw" io="threads" ioeventfd="on" event_idx="off"/&gt;
        &lt;!-- 基于sheepdog的网络源 --&gt;
        &lt;source protocol="sheepdog" name="image_name"&gt;
            &lt;host name="hostname" port="7000"/&gt;&lt;!-- 磁盘源所在的主机 --&gt;
        &lt;/source&gt;
        &lt;target dev="hdb" bus="ide"/&gt;
        &lt;boot order='1'/&gt;
        &lt;transient/&gt;
        &lt;address type='drive' controller='0' bus='1' unit='0'/&gt;
    &lt;/disk&gt;
    &lt;disk type='network'&gt;
        &lt;driver name="qemu" type="raw"/&gt;
        &lt;source protocol="rbd" name="image_name2"&gt;
            &lt;host name="hostname" port="7000"/&gt;
            &lt;snapshot name="snapname"/&gt;
            &lt;config file="/path/to/file"/&gt;
        &lt;/source&gt;
        &lt;target dev="hdc" bus="ide"/&gt;
        &lt;!-- 提供网络身份验证信息 --&gt;
        &lt;auth username='myuser'&gt;
            &lt;secret type='ceph' usage='mypassid'/&gt;
        &lt;/auth&gt;
    &lt;/disk&gt;
    &lt;!-- 定义一个光驱设备 --&gt;
    &lt;disk type='block' device='cdrom'&gt;
        &lt;driver name='qemu' type='raw'/&gt;
        &lt;target dev='hdd' bus='ide' tray='open'/&gt;
    &lt;/disk&gt;
    &lt;!-- 定义一个光驱设备，使用ISO镜像文件 --&gt;
    &lt;disk type='file' device='cdrom'&gt;
        &lt;driver name='qemu' type='raw'/&gt;
        &lt;!-- 使用镜像文件 --&gt;
        &lt;source file='/home/alex/Vmware/KVM/coreos.iso'/&gt;
        &lt;target dev='hdd' bus='ide'/&gt;
        &lt;readonly/&gt;
    &lt;/disk&gt;
    &lt;!-- 从libvirt管理的存储池中取得源 --&gt;
    &lt;disk type='volume' device='disk'&gt;
        &lt;driver name='qemu' type='qcow2'/&gt;
        &lt;source pool="default" volume="coreos.qcow2" /&gt;
        &lt;target dev='vda' bus='virtio'/&gt;
        &lt;boot order='1'/&gt;
    &lt;/disk&gt;
    &lt;!-- 来自iscsi池的源 --&gt;
    &lt;disk type='network' device='disk'&gt;
        &lt;driver name='qemu' type='raw'/&gt;
        &lt;source protocol='iscsi' name='iqn.gmem.cc.example:iscsi-nopool/2'&gt;
            &lt;host name='example.com' port='3260'/&gt;
        &lt;/source&gt;
        &lt;auth username='myuser'&gt;
            &lt;secret type='iscsi' usage='libvirtiscsi'/&gt;
        &lt;/auth&gt;
        &lt;target dev='vda' bus='virtio'/&gt;
    &lt;/disk&gt;
    &lt;!-- 来自iscsi池的源，lun与disk不同的是，客户机的请求直接pass through到物理设备 --&gt;
    &lt;disk type='network' device='lun'&gt;
        &lt;driver name='qemu' type='raw'/&gt;
        &lt;source protocol='iscsi' name='iqn.gmem.cc.example:iscsi-nopool/1'&gt;
            &lt;host name='example.com' port='3260'/&gt;
        &lt;/source&gt;
        &lt;auth username='myuser'&gt;
            &lt;secret type='iscsi' usage='libvirtiscsi'/&gt;
        &lt;/auth&gt;
        &lt;target dev='sdb' bus='scsi'/&gt;
    &lt;/disk&gt;
    &lt;disk type='file' device='disk'&gt;
        &lt;driver name='qemu' type='qcow2'/&gt;
        &lt;source file='/var/lib/libvirt/images/domain.qcow'/&gt;
        &lt;!-- 多级嵌套的backing store --&gt;
        &lt;backingStore type='file'&gt;
            &lt;format type='qcow2'/&gt;
            &lt;source file='/var/lib/libvirt/images/snapshot.qcow'/&gt;
            &lt;backingStore type='block'&gt;
                &lt;format type='raw'/&gt;
                &lt;source dev='/dev/mapper/base'/&gt;
                &lt;backingStore/&gt;
            &lt;/backingStore&gt;
        &lt;/backingStore&gt;
        &lt;target dev='vdd' bus='virtio'/&gt;
    &lt;/disk&gt;
&lt;/devices&gt;</pre>
<div class="blog_h3"><span class="graybg">disk元素属性</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>type</td>
<td>
<p>磁盘的来源（source）类型。有效值：file,block,dir,network,volume</p>
<p>其中volume表示磁盘的来源是存储池中的一个卷</p>
</td>
</tr>
<tr>
<td>device</td>
<td>
<p>客户机看到的磁盘类别。有效值：floppy,disk,cdrom,lun，默认disk</p>
<p>仅当type=block|network且protocol=iscsi时；或者type=volume且使用protocol=iscsi、mode=host的source pool时可设置为lun，lun的行为与disk相同，除了来自客户机的SCSI命令被接收并pass through到物理设备。lun仅被raw设备识别，不能被分区识别</p>
</td>
</tr>
<tr>
<td>rawio</td>
<td>仅当device=lun时使用，是否启用rawio，有效值：yes/no</td>
</tr>
<tr>
<td>snapshot</td>
<td>指定快照行为：<br />internal    使用内部快照<br />external    使用外部快照<br />no    该磁盘不参与快照</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg"> source子元素</span></div>
<p> 指定磁盘的来源（source），其包含的属性依赖于disk的type属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 12%; text-align: center;">disk.type</td>
<td style="width: 12%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>file</td>
<td>file</td>
<td>指定虚拟磁盘对应的镜像文件的全限定路径</td>
</tr>
<tr>
<td>block</td>
<td>dev</td>
<td>指定虚拟设备映射到的宿主机块设备的全限定路径</td>
</tr>
<tr>
<td>dir</td>
<td>dir</td>
<td>指定宿主机目录，此目录作为客户机的磁盘使用</td>
</tr>
<tr>
<td rowspan="2">network</td>
<td>protocol</td>
<td>访问磁盘映像的协议，有效值：nbd、iscsi、rbd、sheepdog、gluster</td>
</tr>
<tr>
<td>name</td>
<td>如果protocol=rbd、sheepdog、gluster，则此属性必须，用于指定什么卷/镜像被使用<br />对于protocol=iscsi，name可能包含一个逻辑单元号（ logical unit number，LUN），例如xx:iscsi-pool/1，不指定LUN则默认0</td>
</tr>
<tr>
<td rowspan="3">volume</td>
<td>pool</td>
<td>指定由libvirt管理的存储池（storage pool）的名称，磁盘来源位于此池中</td>
</tr>
<tr>
<td>volume</td>
<td>指定用作磁盘来源的，由libvirt关联的存储卷（ storage volume ）的名称</td>
</tr>
<tr>
<td>mode</td>
<td>指示如何将LUN暴露为磁盘来源：<br />direct<br />host，默认值</td>
</tr>
</tbody>
</table>
<p>source子元素可以包含以下子元素：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>host</td>
<td>当disk.type=network时，可以包含若干各host子元素，用来指定需要连接的主机。host具有以下属性：<br />name，主机名<br />port，监听端口<br />transport，传输协议类型<br />socket，UNIX套接字路径</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">backingStore子元素</span> </div>
<p>紧跟着source元素，用于指定磁盘使用的backing store ，backing store是构成磁盘的逻辑成分（类似于QEMU的backing file）。如果不指定此元素，则意味着source是自包含的。backingStore元素的属性列表如下：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>type</td>
<td>backing store使用的磁盘类型，类似于disk.type</td>
</tr>
<tr>
<td>index</td>
<td> </td>
</tr>
</tbody>
</table>
<p>backingStore可以有下列子元素：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>format</td>
<td>其type属性指定backing store内部的镜像格式，例如raw、qcow2</td>
</tr>
<tr>
<td>source</td>
<td>类似于disk.source</td>
</tr>
<tr>
<td>backingStore</td>
<td>如果此backing store也不是自包含，而依赖于其它backing store时，使用此元素递归的指定</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">target子元素</span></div>
<p>此子元素控制虚拟磁盘在什么总线/设备下暴露给客户机。属性如下：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>dev</td>
<td>磁盘在客户机下的逻辑名称，此名称并不确保映射到相应的设备名称，你只能将其作为设备的“顺序提示”</td>
</tr>
<tr>
<td>bus</td>
<td>设置模拟的磁盘类型，有效值包括ide、scsi、virtio、xen、usb、sata、sd。如果不指定，bus的值根据dev的风格来推断，例如hda可以推断出ide</td>
</tr>
<tr>
<td>tray</td>
<td>可移动磁盘（光盘、软盘）的一个状态字段，有效值包括open、closed，默认closed</td>
</tr>
<tr>
<td>removable</td>
<td>设置USB磁盘的可移除标记，on/off，默认off</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">iotune子元素</span></div>
<p>针对单块磁盘进行IO微调，与 blkiotune 功能类似，但是后者针对Domain全局。</p>
<p>目前可设置的微调项都是针对QEMU的IO throttling微调，这些微调由子元素指定，取值0表示无限制。子元素列表：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 22%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>total_bytes_sec</td>
<td rowspan="3">每秒钟I/O吞吐量的限制。total_bytes_sec不能与后两者同时出现</td>
</tr>
<tr>
<td>read_bytes_sec</td>
</tr>
<tr>
<td>write_bytes_sec</td>
</tr>
<tr>
<td>total_iops_sec</td>
<td rowspan="3">每秒钟I/O操作次数的限制。total_iops_sec不能与后两者同时出现</td>
</tr>
<tr>
<td>read_iops_sec</td>
</tr>
<tr>
<td>write_iops_sec</td>
</tr>
<tr>
<td>total_bytes_sec_max</td>
<td rowspan="6">与上面类似，但是限制最大值</td>
</tr>
<tr>
<td>read_bytes_sec_max</td>
</tr>
<tr>
<td>write_bytes_sec_max</td>
</tr>
<tr>
<td>total_iops_sec_max</td>
</tr>
<tr>
<td>write_iops_sec_max</td>
</tr>
<tr>
<td>size_iops_sec</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">driver子元素</span></div>
<p>与hypervisor驱动相关的更多细节配置，属性列表：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>name    type</td>
<td>
<p>如果hypervisor支持多个backend驱动，则name属性指定primary后端驱动的名称，而type则指定一个子类型。例如：</p>
<ol>
<li>xen支持的name有tap、tap2、phy、file，支持type有aio</li>
<li>QEMU仅支持name为qemu，而支持raw、bochs、qcow2、qed等type</li>
</ol>
</td>
</tr>
<tr>
<td>cache</td>
<td>
<p>控制IO缓存策略，有效值包括</p>
<ol style="list-style-type: undefined;">
<li>default 由hypervisor自动选择。qemu-kvm 1.2-默认writethrough。之后的版本，对于客户机驱动ide/scsi/virtio来说，默认值可能是 writethrough</li>
<li>none 相当于直接使用宿主机的物理磁盘缓存，性能不错</li>
<li>writethrough 数据<span style="background-color: #c0c0c0;">直接写入磁盘（O_DSYNC）里，不使用缓存</span>；在<span style="background-color: #c0c0c0;">数据更新时，同时写入缓存Cache和后端存储</span>。此模式的优点是操作简单；缺点是因为数据修改需要同时写入存储，数据写入<span style="background-color: #c0c0c0;">速度较慢</span></li>
<li>writeback 在数据更新时<span style="background-color: #c0c0c0;">只写入缓存Cache（不使用O_DSYNC、O_DIRECT）</span>。只在<span style="background-color: #c0c0c0;">数据被替换出缓存时，被修改的缓存数据才会被写到后端存储</span>。此模式的优点是数据<span style="background-color: #c0c0c0;">写入速度快</span>，因为不需要写存储；缺点是一旦<span style="background-color: #c0c0c0;">更新后的数据未被写入存储时出现系统掉电的情况</span>，数据将无法找回</li>
<li>directsync，写入磁盘时，qemu-kvm将使用O_DSYNC + O_DIRECT。速度慢</li>
<li>unsafe 任何时候都不要在生产环境使用，cache flush不会传播到宿主机，因此任何意外的VM关机都会摧毁虚拟机文件系统</li>
</ol>
<p>最后两种几乎不会使用</p>
<p>writethrough、none、directsync的安全性好，只要客户机操作系统是现代且行为正常的 —— 必要时会执行flush</p>
<p>writeback的安全性次之，它给提示后端写缓存的存在，依赖于客户机发送必须的flush命令来保证客户机磁盘的数据完整性 —— 这是现代文件系统应有的正常行为。但是，在报告（给客户端应用程序）IO操作完成，到数据提交到宿主机磁盘，存在一个时间窗口。如果宿主机宕机，可能导致数据丢失</p>
<p>unsafe安全性差，和writeback的差异在于，客户机的flush命令被忽略</p>
<p>性能上： writeback &gt; none &gt; writethrough</p>
</td>
</tr>
<tr>
<td>error_policy</td>
<td>当磁盘出现读写错误时hypervisor的处理策略，有效值：stop、report、ignore、enospace</td>
</tr>
<tr>
<td>io</td>
<td>
<p>控制AIO的策略，QEMU支持：</p>
<ol style="list-style-type: undefined;">
<li>threads：用户空间异步IO的实现，其实它不是真正的异步IO，是通过启动一定数量的 blocking IO线程来模拟异步IO</li>
<li>native：Kernel native AIO :，Kernel的原生态异步IO实现</li>
</ol>
<p>native的性能更好</p>
</td>
</tr>
<tr>
<td>copy_on_read</td>
<td>
<p>当读取backing文件时，是否将读取的内容复制到当前的镜像文件中，当backing文件位于慢速网络中时可以设置为on</p>
<p>仅用于QEMU/KVM</p>
</td>
</tr>
<tr>
<td>iothread</td>
<td>将磁盘分配给Domain的iothreads元素定义的IO线程</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">boot子元素</span></div>
<p>用于指定该磁盘是可启动的，order属性指定其启动顺序。</p>
<div class="blog_h3"><span class="graybg">encryption子元素</span></div>
<p>指定卷如何被加密。</p>
<div class="blog_h3"><span class="graybg">readonly子元素</span></div>
<p>指定此磁盘不能被客户机修改，对于device=cdrom的设备默认true。</p>
<div class="blog_h3"><span class="graybg">shareable子元素</span></div>
<p>假设hypervisor和OS支持的话，指示此设备可以被多个Domain共享。指定此元素，应当同时禁用磁盘的缓存。</p>
<div class="blog_h3"><span class="graybg">transient子元素</span></div>
<p>指示当客户机退出时，对磁盘的所有修改将回退。对于某些hypervisor，把磁盘标记为transient会禁止快照与迁移。</p>
<div class="blog_h3"><span class="graybg">serial子元素</span></div>
<p>指定磁盘的序列号。</p>
<div class="blog_h3"><span class="graybg">wwn子元素</span></div>
<p>指定磁盘的世界范围名称（World Wide Name），此值必须唯一，由16位16进制数字组成。</p>
<div class="blog_h3"><span class="graybg">vendor子元素</span></div>
<p>指定磁盘的生产厂商，不超过8个可打印字符</p>
<div class="blog_h3"><span class="graybg">product子元素</span></div>
<p>指定磁盘的产品名称，不超过16个可打印字符</p>
<div class="blog_h3"><span class="graybg">address子元素</span></div>
<p>很多设备可以提供一个address 子元素，来指明设备挂载客户机虚拟总线的什么位置上。如果不指定address，libvirt会生成一个合适的地址。该子元素的属性列表如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>type</td>
<td>必须，有效值包括：<br />pci    可配额外属性domain/bus/slot/function/multifunction<br />drive    可配额外属性controller/bus/target/unit<br />ccid     用于智能卡，可配额外属性bus/slot<br />usb    可配额外属性bus/port，其中port位点号分隔的单字节数字，例如1.2或者2.1.3.1</td>
</tr>
<tr>
<td>bus</td>
<td>磁盘绑定到的总线，对于PCI范围在0-0xff之间，对于其它为2位的bus号</td>
</tr>
<tr>
<td>slot</td>
<td>磁盘绑定到总线上的slot，范围在0x0-0x1f之间，对于其它为2位的slot号</td>
</tr>
<tr>
<td>function</td>
<td>磁盘的function号，范围在0-7之间</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">auth子元素</span></div>
<p>对于disk.type=network，且protocol为rbd、iscsi的磁盘，可以指定此子元素，提供访问磁盘源时使用的凭据。</p>
<div class="blog_h3"><span class="graybg">blockio子元素</span></div>
<p>用于QEMU/KVM，覆盖块设备的属性。属性列表：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>logical_block_size</td>
<td>报告给客户机的逻辑块大小。对于Linux来说，BLKSSZGET ioctl会返回此值，表示最小单元的磁盘IO大小</td>
</tr>
<tr>
<td>physical_block_size</td>
<td>报告给客户机的物理块大小。对于Linux来说，BLKPBSZGET ioctl会返回此值，表示硬件扇区的大小</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">文件系统配置</span></div>
<p>使用filesystem元素可以把宿主机上的目录直接暴露给客户机访问，配置示例：</p>
<pre class="crayon-plain-tag">&lt;devices&gt;
    &lt;filesystem type='template'&gt;
        &lt;source name='my-vm-template'/&gt;&lt;!-- OpenVZ模板名称 --&gt;
        &lt;target dir='/'/&gt;
    &lt;/filesystem&gt;
    &lt;filesystem type='mount' accessmode='passthrough'&gt;
        &lt;driver type='path' wrpolicy='immediate'/&gt;
        &lt;source dir='/export/to/guest'/&gt; &lt;!-- 指定宿主机目录 --&gt;
        &lt;target dir='/import/from/host'/&gt;
        &lt;readonly/&gt;
    &lt;/filesystem&gt;
    &lt;filesystem type='file' accessmode='passthrough'&gt;
        &lt;driver name='loop' type='raw'/&gt;
        &lt;driver type='path' wrpolicy='immediate'/&gt;
        &lt;source file='/export/to/guest.img'/&gt;
        &lt;target dir='/import/from/host'/&gt;
        &lt;readonly/&gt;
    &lt;/filesystem&gt;
&lt;/devices&gt;</pre>
<p>filesystem元素的属性列表：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><span style="color: #000000;">type</span></td>
<td>
<p>指定文件系统的来源，有效值包括：<br />mount    默认值，挂载到客户机的一个宿主机目录，支持LXC、OpenVZ、QEMU/KVM<br />template    OpenVZ模板<br />file    一个宿主机文件被作为镜像，挂载到客户机，仅LXC<br />block    一个宿主机块设备，挂载到客户机，仅LXC<br />ram    一个内存文件系统<br />bind    绑定宿主机中的一个目录到客户机的一个目录，仅LXC</p>
</td>
</tr>
<tr>
<td> accessmode</td>
<td>访问源的安全模式，有效值包括：<br />passthrough    默认值，基于客户机用户的权限访问源<br />mapped    基于hypervisor（QEMU进程）的权限访问源<br />squash     类似于passthrough，只是忽略chown之类操作的错误</td>
</tr>
</tbody>
</table>
<p>子元素列表：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>driver</td>
<td>
<p>指定hypervisor驱动的更多细节，属性列表：<br />type    如果hypervisor支持多种backend驱动，使用该属性指定primary后端驱动的名称<br />format    指定格式类型</p>
<p>对于LXC，支持type=loop&amp;format=raw，或者type=nbd；QEMU支持type=path|handle，不支持format</p>
</td>
</tr>
<tr>
<td>source</td>
<td>标注宿主机上的资源，该资源暴露为客户机的文件系统，属性列表：<br />name    仅用于filesystem.type=template，指定模板的名字<br />dir    仅用于filesystem.type=mount，指定宿主机目录<br />usage    仅用于filesystem.type='ram' 以KiB（可以使用units属性指定单位）限制内存用量</td>
</tr>
<tr>
<td>target</td>
<td>对于QEMU，指定文件系统在客户机的挂载点</td>
</tr>
<tr>
<td>readonly</td>
<td>文件系统对于客户机是否只读，仅QEMU/KVM</td>
</tr>
<tr>
<td>space_hard_limit</td>
<td rowspan="2">文件系统对于客户机的容量软硬限制</td>
</tr>
<tr>
<td>space_soft_limit</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">网络接口配置</span></div>
<p>有几种方式（type）来指定客户机能够看到的网络接口，网络接口的容器元素是<pre class="crayon-plain-tag">interface</pre> 。每个interface元素可以拥有一个address子元素，指定其在PCI上的slot。interface元素的属性列表：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 22%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>type</td>
<td>网络接口的类型，有效值：<br />network    虚拟网络<br />bridge    直接桥接VM到局域网</td>
</tr>
<tr>
<td>trustGuestRxFilters</td>
<td>
<p>如果设置为true，则宿主机能够检测到并信任来自客户机的关于接口MAC地址变更的报告，并接收filters</p>
<p>对此设置的支持，取决于客户机的网络设备型号，以及宿主机的连接类型。当前只有virtio型号和macvtap宿主机连接类型支持</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">虚拟网络</span></div>
<p>对于基于动态地址分配/无线网络的宿主机获得连接性的虚拟机，推荐此方式。</p>
<p>虚拟网络提供一个其详细信息由一个命名网络定义（named network definition）所描述的连接。依据虚拟网络的转发模式（forward mode）设置，它可能是：</p>
<ol>
<li>完全隔离的，不配置<pre class="crayon-plain-tag">&lt;forward&gt;</pre> 元素</li>
<li>NAT到一个指定的网络设备或者默认路由，配置<pre class="crayon-plain-tag">&lt;forward mode='nat'&gt;</pre> </li>
<li>不基于NAT来路由，配置<pre class="crayon-plain-tag">&lt;forward mode='route'/&gt;</pre> </li>
<li>直接连接到宿主机的网络接口（通过macvtap）或桥接设备，配置<pre class="crayon-plain-tag">&lt;forward mode='bridge|private|vepa|passthrough'/&gt;</pre> </li>
</ol>
<p>对于转发模式设置为bridge | private | vepa | passthrough的网络，宿主机在libvirt管理范围之外应拥有必要的DNS、DHCP服务。对于转发模式设置为isolated | nat |routed的网络，libvirt提供的虚拟网络中包含了DHCP和DNS。虚拟网络自动分配的IP地址范围可以通过命令</p>
<pre class="crayon-plain-tag">virsh net-dumpxml [networkname]</pre>
<p>得到。一个<span style="background-color: #c0c0c0;">开箱即用的、称为default的虚拟网络</span>NAT到宿主机默认路由，其IP地址范围是192.168.122.0/24，在宿主机中你可以ifconfig看到一个名为virbr0的网络接口，与这个default虚拟网络有关。要自定义虚拟网络，需要修改其它类型（network XML）的配置文件L。</p>
<p>每个客户机会有一个命名为<pre class="crayon-plain-tag">vnetN</pre> 的tun设备，你可以利用target元素覆盖此命名。</p>
<p>类似于direct类型的接口，network类型的接口可以指定一个virtualport子元素，用于将配置信息转发给vepa（802.1Qbg）或 802.1Qbh兼容的交换机，或Open vSwich虚拟交换机。</p>
<p>配置示例：</p>
<pre class="crayon-plain-tag">&lt;devices&gt;
    &lt;interface type='network'&gt;
        &lt;!-- 如果接口的source是一个网络，则可以设置soure元素的portgroup属性，一个网络可能定义了多个portgroup，
             每个portgroup包含些许不同的配置信息，用于不同类别的网络连接 --&gt;
        &lt;source network='default'/&gt;
    &lt;/interface&gt;
    &lt;interface type='network'&gt;
        &lt;!-- --&gt;
        &lt;source network='default' portgroup='engineering'/&gt;
        &lt;target dev='vnet7'/&gt;  &lt;!-- 设置虚拟网卡的名称 --&gt;
        &lt;mac address="00:11:22:33:44:55"/&gt;&lt;!-- 设置虚拟网卡的MAC地址 --&gt;
        &lt;virtualport&gt;
            &lt;parameters instanceid='09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f'/&gt;
        &lt;/virtualport&gt;
    &lt;/interface&gt;
&lt;/devices&gt;</pre>
<div class="blog_h3"><span class="graybg">桥接到LAN</span></div>
<p>对于基于静态地址的有线网络的宿主机获得连接性的虚拟机，推荐此方式。</p>
<p>该方式将虚拟机直接桥接到宿主机所在的局域网，libvirt假设宿主机上的网桥设备enslaved了1-N个物理网卡。客户机的IP地址范围与宿主机局域网的IP地址范围一样。</p>
<p>在Linux系统中，网桥通常是标准的Linux主机网桥（host bridge）。如果主机支持Open vSwitch，则可以添加<pre class="crayon-plain-tag">&lt;virtualport type='openvswitch'/&gt;</pre> 子元素以连接到Open vSwitch网桥。</p>
<p>每个客户机会有一个命名为<pre class="crayon-plain-tag">vnetN</pre> 的tun设备，你可以利用target元素覆盖此命名。此tun设备会自动enslaved到宿主机网桥。</p>
<p>配置示例：</p>
<pre class="crayon-plain-tag">&lt;interface type='bridge'&gt;
    &lt;!-- 桥接到宿主机的br0网桥 --&gt;
    &lt;source bridge='br0'/&gt;
&lt;/interface&gt;
&lt;interface type='bridge'&gt;
    &lt;source bridge='br1'/&gt;
    &lt;!-- 设置客户机中虚拟网卡的名称和MAC --&gt;
    &lt;target dev='vnet7'/&gt;
    &lt;mac address="00:11:22:33:44:55"/&gt;
&lt;/interface&gt;
&lt;interface type='bridge'&gt;
    &lt;source bridge='ovsbr'/&gt;
    &lt;!-- 连接到Open vSwitch --&gt;
    &lt;virtualport type='openvswitch'&gt;
        &lt;!-- interfaceid是此虚拟网卡的唯一标识，如果不指定自动生成；profileid作为虚拟网卡的port-profile发送给vSwitch --&gt;
        &lt;parameters profileid='menial' interfaceid='09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f'/&gt;
    &lt;/virtualport&gt;
&lt;/interface&gt;</pre>
<div class="blog_h3"><span class="graybg">用户空间SLIRP栈</span></div>
<p>提供一个虚拟局域网并NAT到外面的世界，此虚拟网络使用10.0.2.x网段。默认路由10.0.2.2，DNS服务器10.0.2.3，客户机地址从10.0.2.15开始。此网络仅仅用于没有特权的宿主机用户。配置示例：</p>
<pre class="crayon-plain-tag">&lt;interface type='user'/&gt;
&lt;interface type='user'&gt;
    &lt;mac address="00:11:22:33:44:55"/&gt;
&lt;/interface&gt;</pre>
<div class="blog_h3"><span class="graybg">设置虚拟网卡型号</span></div>
<p>如果hypervisor支持，则可以设置虚拟网卡的型号。示例：</p>
<pre class="crayon-plain-tag">&lt;interface type='network'&gt;
    &lt;model type='ne2k_pci'/&gt;
&lt;/interface&gt;</pre>
<p>QEMU支持的型号包括 ne2k_isa i82551 i82557b i82559er ne2k_pci pcnet rtl8139 e1000 virtio。</p>
<div class="blog_h3"><span class="graybg">修改虚拟网卡状态</span></div>
<p>可以设置网卡是启用还是断开的：</p>
<pre class="crayon-plain-tag">&lt;interface type='network'&gt;
    &lt;link state='down'/&gt;
&lt;/interface&gt;</pre>
<div class="blog_h3"><span class="graybg">IP配置</span></div>
<p>网络设备、具有网络特性的hostdev设备可以配置一个或者多个IP地址，某些hypervisor会忽略这些配置。配置示例：</p>
<pre class="crayon-plain-tag">&lt;devices&gt;
    &lt;interface type='network'&gt;
        &lt;source network='default'/&gt;
        &lt;target dev='vnet0'/&gt;
        &lt;ip address='192.168.122.5' prefix='24'/&gt;
        &lt;!-- peer指定点对点连接中对端的IP地址 --&gt;
        &lt;ip address='192.168.122.5' prefix='24' peer='10.0.0.10'/&gt;
        &lt;!-- route仅用于LXC --&gt;
        &lt;route family='ipv4' address='192.168.122.0' prefix='24' gateway='192.168.122.1'/&gt;
        &lt;route family='ipv4' address='192.168.122.8' gateway='192.168.122.1'/&gt;
    &lt;/interface&gt;
    &lt;hostdev mode='capabilities' type='net'&gt;
        &lt;source&gt;
            &lt;interface&gt;eth0&lt;/interface&gt;
        &lt;/source&gt;
        &lt;ip address='192.168.122.6' prefix='24'/&gt;
        &lt;route family='ipv4' address='192.168.122.0' prefix='24' gateway='192.168.122.1'/&gt;
        &lt;route family='ipv4' address='192.168.122.8' gateway='192.168.122.1'/&gt;
    &lt;/hostdev&gt;
&lt;/devices&gt; </pre>
<div class="blog_h2"><span class="graybg">图形配置</span></div>
<p>配置图形（Graphical）设备，可以让你与客户机进行图形化的交互。客户机通常提供一个framebuffer或者text console，作为人机接口。配置示例：</p>
<pre class="crayon-plain-tag">&lt;!-- 基于SDL的图形接口，弹出QEMU的图形化控制台窗口 --&gt;
&lt;graphics type='sdl' display=':0' xauth='/home/alex/.Xauthority'/&gt;
&lt;!-- 基于VNC的图形接口 --&gt;
&lt;graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0' sharePolicy='allow-exclusive'&gt;
    &lt;listen type='address' address='0.0.0.0'/&gt;
&lt;/graphics&gt;

&lt;graphics type='rdp' autoport='yes' multiUser='yes'/&gt;
&lt;graphics type='desktop' fullscreen='yes'/&gt;
&lt;graphics type='spice'&gt;
    &lt;listen type='network' network='rednet'/&gt;
&lt;/graphics&gt;</pre>
<p>根据强制属性type的取值，grpahics的属性、子元素有所差异：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 10%; text-align: center;">type</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>sdl</td>
<td>在宿主机的桌面上显示一个窗口，额外属性：<br />display    使用哪个显示器<br />xauth    验证标识符<br />fullscreen    是否全屏，yes/no</td>
</tr>
<tr>
<td>vnc</td>
<td>启动一个VNC服务器，额外属性：<br />port    监听端口，-1表示自动分配<br />autoport    表示自动分配端口<br />passwd    VNC密码明文<br />keymap    使用的keymap<br />passwdValidTo    密码有效期限（UTC），示例'2010-04-09T15:51:00'<br />sharePolicy    显示共享策略，allow-exclusive独占并丢弃其它连接，force-shared禁止独占，ignore无条件允许任何连接<br />socket    对于QEMU，可以指定一个UNIX domain socket而非TCP/IP</td>
</tr>
<tr>
<td>spice</td>
<td>
<p>启动一个SPICE服务器，额外属性：<br />port   监听端口，-1表示自动分配<br />tlsPort    安全协议端口<br />autoport   表示自动分配端口<br />passwd   SPICE密码明文<br />keymap    使用的keymap<br />passwdValidTo    密码有效期限（UTC），示例'2010-04-09T15:51:00'<br />connected     如果密码改变，如何控制已连接的客户端。keep保持连接，disconnect断开连接，fail禁止修改密码<br />defaultMode    设置默认的通道安全策略。有效值secure、insecure、any</p>
<p>如果SPICE同时配置了普通端口、TLS安全端口。则可以利用channel子元素限制某个通道使用的端口。可用的通道名包括main, display, inputs, cursor, playback, record,smartcard,usbredir。配置示例：</p>
<pre class="crayon-plain-tag">&lt;channel name='main' mode='secure'/&gt;
&lt;channel name='record' mode='insecure'/&gt;</pre>
<p>SPICE支持音频、图片、流的压缩。你可以设置以下子元素的compression属性：<br />image    图片压缩，支持compression取值auto_glz, auto_lz, quic, glz, lz, off<br />jpeg    基于WAN访问时的JPEG压缩，支持compression取值 auto, never, always<br />zlib，基于WAN访问时的图片压缩，支持compression取值auto, never, always<br />playback，音频流压缩，支持compression取值onn,off</p>
<p>配置示例：</p>
<pre class="crayon-plain-tag">&lt;image compression='auto_glz'/&gt; </pre>
<p>可以使用streaming子元素设置流模式，其mode属性可以取值filter, all,off，配置示例：</p>
<pre class="crayon-plain-tag">&lt;streaming mode='filter'/&gt;</pre>
<p>基于Spice agent的复制/粘贴功能可以利用clipboard子元素设置，默认启用，配置示例：</p>
<pre class="crayon-plain-tag">&lt;clipboard copypaste='no'/&gt; </pre>
<p>鼠标模式可以利用mouse子元素设置，mode取值client,server，配置示例：</p>
<pre class="crayon-plain-tag">&lt;mouse mode='client'/&gt;</pre>
<p>文件传输功能可以利用filetransfer设置，默认启用，配置示例：</p>
<pre class="crayon-plain-tag">&lt;filetransfer enable='no'/&gt;</pre>
<p>SPICE支持服务器端的基于OpenGL的加速渲染（仅QEMU），配置示例：<pre class="crayon-plain-tag">&lt;gl enable='yes'/&gt;</pre> </p>
</td>
</tr>
<tr>
<td>rdp</td>
<td>启用一个RDP服务器，额外属性：<br />port 监听端口，-1表示自动分配<br />autoport 表示自动分配端口<br />replaceUser    布尔值，是否允许多个用户同时连接</td>
</tr>
<tr>
<td>desktop</td>
<td>保留给VirtualBox Domain，配置类似于sdl</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">listen子元素</span></div>
<p>用于指明在何处监听客户机连接。</p>
<div class="blog_h2"><span class="graybg">视频设备配置</span></div>
<p>video是描述视频设备的容器，为了向后兼容，如果配置了graphics却没有配置video，libvirt会根据客户机的类型自动添加一个video。配置示例：</p>
<pre class="crayon-plain-tag">&lt;!-- 默认第一个配置video是主视频设备，可以用primary属性覆盖 --&gt;
&lt;video primary='yes'&gt;
    &lt;model type='vga' vram='16384' heads='1'&gt;
        &lt;acceleration accel3d='yes' accel2d='yes'/&gt;
    &lt;/model&gt;
&lt;/video&gt;
&lt;video&gt;
   &lt;!-- 下面的配置是KVM客户机的默认值 --&gt;
   &lt;model type='cirrus' vram='16384' heads='1' /&gt;
&lt;/video&gt;</pre>
<p>子元素说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>model</td>
<td>type     视频设备类型，可选值：vga、cirrus、vmvga、xen、vbox、qxl、virtio，基于hypervisor来选择<br />vram    现存容量<br />heads   设置屏幕的数量</td>
</tr>
<tr>
<td>acceleration</td>
<td>accel2d    启用2D加速，仅vbox<br />accel3d    启用3D加速，仅vbox、QEMU</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg"> 使用libguestfs</span></div>
<p>libguestfs是一组工具集，用来（在不启动客户机的情况下）访问、修改虚拟机的磁盘文件，通过libguestfs你可以好对磁盘进行以下操作：</p>
<ol>
<li>查看或者修改文件</li>
<li>创建虚拟磁盘</li>
<li>改变虚拟磁盘大小</li>
<li>执行磁盘备份、克隆等操作</li>
</ol>
<p>libguestfs支持多种虚拟磁盘格式，包括Vmware、Hyper-V。日常工作中我们主要使用libguestfs提供的命令行guestfish。libguestfs不依赖于libvirt。</p>
<p>与libguestfs类型工具包括：</p>
<ol>
<li>kpartx 需要root权限，并且将文件系统挂载到宿主机的内核中。相比之下libguestfs把文件系统隔离在appliance中，安全性高</li>
<li>vdfuse 该工具类似于kpartx，但是仅仅针对VirtualBox虚拟磁盘</li>
<li>qemu-nbd  用QEMU提供的工具，基于QEMU支持的磁盘格式（raw、qcow2）构建网络块服务器。libguestfs可以与之配合使用：<br />
<pre class="crayon-plain-tag">guestfish -a nbd://remote</pre>
</li>
</ol>
<div class="blog_h2"><span class="graybg">安装与配置</span></div>
<p>执行下面的命令安装libguestfs：</p>
<pre class="crayon-plain-tag">sudo apt-get install libguestfs-tools</pre>
<p>在Ubuntu下，需要执行：</p>
<pre class="crayon-plain-tag">sudo chmod 0644 /boot/vmlinuz* </pre>
<p>否则在使用过程中你会遇到cp: cannot open '/boot/vmlinuz-4.4.0-38-generic' for reading: Permission denied错误。</p>
<div class="blog_h2"><span class="graybg">使用guestfish</span></div>
<p>执行下面的命令，以编辑一个虚拟磁盘：</p>
<pre class="crayon-plain-tag"># 附加-v参数，可以看到很多调试信息，例如appliance的启动过程日志
guestfish -a vda.qcow2

# 出现提示符
&gt;&lt;fs&gt; 

# 添加一个磁盘，只能在run之前执行
# add-drive filename [readonly:true|false] [format:..] [iface:..] [name:..] [label:..] [protocol:..] [server:..]
add-drive vdb,qcow2 format:qcow2

# 执行run命令，一个appliance（类似于微型虚拟机）会被启动
&gt;&lt;fs&gt; run

# 列出设备
&gt;&lt;fs&gt; list-devices
# 输出：/dev/sda

# 列出分区
&gt;&lt;fs&gt; list-partitions
# 输出：
# /dev/sda1
# /dev/sda2

# 显示各分区详细信息
&gt;&lt;fs&gt; part-list /dev/sda
# [0] = {
#   part_num: 1
#   part_start: 1048576
#   part_end: 525336575
#   part_size: 524288000
# }
# [1] = {
#   part_num: 2
#   part_start: 525336576
#   part_end: 17179869183
#   part_size: 16654532608
# }
# 测试第一个分区是否可启动
&gt;&lt;fs&gt; part-get-bootable /dev/sda 1
# 其它分区相关命令： part-add，part-del，part-disk，part-get-bootable，part-get-gpt-type，part-get-mbr-id，
#                 part-get-name，part-get-parttype，part-init，part-list，part-set-bootable，part-set-gpt-type，
#                 part-set-mbr-id，part-set-name，part-to-dev，part-to-partnum

# 操作LVM
# 显示物理卷详细信息
pvs-full 
# 显示逻辑卷组详细信息
vgs-full
# 其它LVM相关命令：
# lvcreate, lvcreate-free, lvm-canonical-lv-name, lvm-clear-filter, lvm-remove-all, lvm-set-filter, 
# lvremove, lvrename, lvresize, lvresize-free, lvs, lvs-full, lvuuid, pvcreate, pvremove, pvresize, 
# pvresize-size, pvs, pvs-full, pvuuid, vg-activate, vg-activate-all, vgchange-uuid, vgchange-uuid-all, 
# vgcreate, vglvuuids, vgmeta, vgpvuuids, vgremove, vgrename, vgs, vgs-full, vgscan, vguuid
# 列出文件系统
&gt;&lt;fs&gt; list-filesystems
# 输出：
# /dev/sda1: ext4
# /dev/fedora_bogon/root: xfs
# /dev/fedora_bogon/swap: swap

# 挂载文件系统
&gt;&lt;fs&gt; mount /dev/fedora_bogon/root /

# 列出目录
&gt;&lt;fs&gt; ls /
# 创建新目录
&gt;&lt;fs&gt; mkdir /temp
# 其它支持的文件系统命令包括：cp chown chmod cp

# 下载文件到当前目录
download /root/.bash_history test
# 上传文件到虚拟磁盘
upload test /temp/test

# 查看文件内容
cat /temp/test

# 退出
&gt;&lt;fs&gt; quit</pre>
<p>执行run子命令后，需要等待一会，这时libguestfs在启动一个 appliance。再此appliance中会运行一个Linux内核，LVM、ext2等用户空间工具，以及一个守护程序guestfsd。宿主机进程基于RPC与这个守护进程通信，完成对磁盘镜像的操作。</p>
<div class="blog_h2"><span class="graybg">使用Guestmount</span></div>
<p>该命令可以把虚拟磁盘挂载到宿主机的目录上：</p>
<pre class="crayon-plain-tag"># 创建挂载点
mkdir vda
# 挂载一个文件系统
guestmount -a vda.qcow2 --rw -m /dev/fedora_bogon/root vda
# 现在你可以在宿主机中修改虚拟磁盘中的文件
# 操作完毕后，取消挂载
guestunmount vda</pre>
<div class="blog_h2"><span class="graybg">使用virt-builder</span></div>
<p>该命令可以用来快速的创建虚拟机磁盘镜像：</p>
<pre class="crayon-plain-tag"># 列出所有支持的客户机类型
virt-builder --list
# 创建一个Fedora 24的虚拟磁盘镜像，第一次使用某个客户机类型，需要从网络上下载镜像文件
virt-builder fedora-24 
    -o fedora-24.qcow2 --format qcow2 --size 20G 
    --hostname fedora-24-01   # 设置主机名
    --root-password file:/tmp/pswd  # 设置root密码，密码从文件中读取
    --install "apache2"  # 安装软件
    --firstboot  /tmp/fb.sh  # 第一次启动时执行的脚本</pre>
<div class="blog_h2"><span class="graybg">其它命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>virt-ls</td>
<td>列出虚拟磁盘中的文件，示例：<pre class="crayon-plain-tag">virt-ls -a vda.qcow2 /</pre> </td>
</tr>
<tr>
<td>virt-cat</td>
<td>查看虚拟磁盘中某个文件的内容，示例：<pre class="crayon-plain-tag">virt-cat -a vda.qcow2 /root/.bashrc</pre> </td>
</tr>
<tr>
<td>virt-copy-in</td>
<td>拷贝目录或者文件到虚拟磁盘中，示例：<pre class="crayon-plain-tag">virt-copy-in -a vda.qcow2 hello /root/</pre></td>
</tr>
<tr>
<td>virt-copy-out </td>
<td>从虚拟磁盘中拷贝文件到本地，示例：<pre class="crayon-plain-tag">virt-copy-out -a vda.qcow2 /root/hello .</pre> </td>
</tr>
<tr>
<td>virt-df</td>
<td>显示虚拟磁盘文件系统的剩余空间情况</td>
</tr>
<tr>
<td>virt-diff</td>
<td>显示两个Domain或者虚拟磁盘中同一文件的差异</td>
</tr>
<tr>
<td>virt-edit</td>
<td>编辑一个文件</td>
</tr>
<tr>
<td>virt-format</td>
<td>执行格式化操作</td>
</tr>
<tr>
<td>virt-inspector</td>
<td>显示虚拟磁盘中操作系统的版本、以及其它信息</td>
</tr>
<tr>
<td>virt-make-fs</td>
<td>从一系列文件，或者tar来创建一个文件系统</td>
</tr>
<tr>
<td>virt-resize</td>
<td>改变虚拟磁盘的尺寸</td>
</tr>
<tr>
<td>virt-sparsify</td>
<td>稀疏化，虚拟磁盘中的空闲空间将归还给宿主机</td>
</tr>
<tr>
<td>virt-tar-in</td>
<td>打包并上传</td>
</tr>
<tr>
<td>virt-tar-out</td>
<td>打包并下载</td>
</tr>
<tr>
<td>virt-win-reg</td>
<td>导入导出Windows的注册表键值</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">管理虚拟局域网</span></div>
<p>通过配置libvirt的虚拟局域网，可以简化Domain的网络接口配置，比QEMU的脚本方式好很多。此外虚拟局域网还提供DHCP服务。</p>
<div class="blog_h2"><span class="graybg">虚拟网络交换机</span></div>
<p>libvirt引入了virtual network switch的概念，这是运行在宿主机上的软件。客户机可以“插入”到这个交换机上并传递流量。在Linux宿主机上，这个交换机表现为网络接口——默认情况下名字为virbr0，这个接口实质上是一个网桥。</p>
<div class="blog_h3"><span class="graybg">NAT</span></div>
<p>默认情况下虚拟网络交换机工作在NAT模式下，实质上是基于宿主机的iptables设置IP遮掩（不使用SNAT/DNAT），客户机对外通信时，使用宿主机的IP地址。</p>
<div class="blog_h3"><span class="graybg">路由模式</span></div>
<p>与NAT不同，这种模式直接转发客户机的IP封包，不进行NAT转换。这需要物理网络的路由器配置适当的路由，让客户机子网的流量流向所在的宿主机。</p>
<div class="blog_h3"><span class="graybg">隔离模式</span></div>
<p>这种模式下，虚拟网络交换机不把客户机的IP封包转发到真实网络上去。只有宿主机、各客户机之间可以进行通信。</p>
<div class="blog_h3"><span class="graybg">DNS &amp;DHCP</span></div>
<p>每个虚拟网络交换机可以设置一个用于动态分配的IP地址范围，供连接到此交换机的客户机使用，客户机可以通过DHCP服务自动获得IP地址。</p>
<p>libvirt基于dnsmasq实现DHCP和DNS，对于每个需要DHCP的虚拟网络交换机，libvirt在宿主机上启动一个dnsmasq实例。</p>
<p>除了简单的DNS请求转发，dnsmasq可以做更多的事情：</p>
<ol>
<li>它可以读取宿主机的 /etc/hosts 中条目，来应答DNS查询请求</li>
</ol>
<div class="blog_h2"><span class="graybg">网络架构示意</span></div>
<p>一个可能的虚拟网络架构的逻辑图如下：</p>
<p><img class="aligncenter size-full wp-image-13475" src="https://blog.gmem.cc/wp-content/uploads/2015/08/libvirt-net-logical.png" alt="libvirt-net-logical" width="679" height="498" /></p>
<p>对应的物理拓扑如下：</p>
<p><img class="aligncenter size-full wp-image-13476" src="https://blog.gmem.cc/wp-content/uploads/2015/08/libvirt-net-physical.png" alt="libvirt-net-physical" width="674" height="513" /></p>
<p>其中：</p>
<ol>
<li>VLAN 1，这个虚拟局域网通过网桥virbr0与eth1进行桥接，并基于NAT连接到真实局域网lan2</li>
<li>VLAN 2，这个虚拟局域网桥接到virbr0，但是与真实局域网完全隔离</li>
<li>Guest A，该虚拟机的：
<ol>
<li>eth0桥接到宿主机的网桥eth0，后者直接连接到真实网卡pth0，从而与lan1互联</li>
<li>eth1桥接到virbr0，可以基于NAT受限访问lan2</li>
</ol>
</li>
<li>Guest B，基于NAT、隔离网络</li>
<li>Guest C，基于隔离网络</li>
</ol>
<div class="blog_h2"><span class="graybg">相关virsh子命令</span></div>
<pre class="crayon-plain-tag"># 列出所有活动的虚拟局域网，libvrit默认创建一个桥接到virbr0的vlan，名为default
virsh net-list
#  Name                 State      Autostart     Persistent
# ----------------------------------------------------------
#  default              active     yes           yes

# 列出所有虚拟局域网
virsh net-list --all

# 显示一个虚拟局域网的详细信息
net-info default
# Name:           default
# UUID:           e9e308a3-dea9-46dc-bc92-fad27f2a970a
# Active:         yes
# Persistent:     yes
# Autostart:      yes
# Bridge:         virbr0

# 导出一个虚拟局域网的XML
virsh net-dumpxml default

# 定义一个新的虚拟局域网
virsh net-define /home/alex/Vmware/KVM/vlans/default.xml
# 解除虚拟局域网定义
virsh net-undefine default

# 关闭一个虚拟局域网
virsh net-destroy default

# 将一个虚拟局域网标记为自动启动
net-autostart default

# 启动虚拟局域网，启动后，宿主机上可能出现一个网桥设备
net-start default</pre>
<div class="blog_h2"><span class="graybg">虚拟局域网配置</span></div>
<p>类似于Domain，虚拟局域网的配置也是存放在XML文件中的。每个虚拟局域网对应一个XML配置文件，根元素为network。包括属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 22%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ipv6</td>
<td>yes/no，设置为yes时定义一个虚拟网络，该网络不指定网关的IPv6地址，但是允许客户机-客户机之间的通讯</td>
</tr>
<tr>
<td>trustGuestRxFilters</td>
<td>yese/no，设置连接到此VLAN的Domain的interface元素的同名属性</td>
</tr>
</tbody>
</table>
<p>包含以下基本子元素：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>此VLAN的短名，此名字被用作构成持久化配置文件的名字</td>
</tr>
<tr>
<td>uuid</td>
<td>全局唯一的VLAN标识符，例如3e3fce45-4f53-4fa7-bb32-11f34168b82a</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">连通性配置</span></div>
<p>以下子元素用于配置VLAN的连通性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>bridge</td>
<td>说明如何创建让VLAN连接到物理网络的、宿主机上的网桥设备（虚拟网络交换机）：<br /><strong>name</strong>  <br />网桥设备的名称，这样所有使用此VLAN的客户机可以相互通信，网桥本身可以连接到真实局域网（LAN），取决于宿主机的配置<br />当使用mode=nat|route指定forward子元素，或者不指定forward子元素（隔离的VLAN）时，如果你不指定name属性，则libvirt会自动创建为网桥生成唯一名称并记住。libvirt推荐以virbr前缀指定name<br /><strong>stp</strong><br />是否启用 Spanning Tree Protocol，默认on<br /><strong>delay</strong><br />网桥转发的延迟秒数，默认0<br /><strong>macTableManager</strong><br />告知libvirt，如何管理MAC地址表（用于判断数据包的出口），默认值kernel，可设置为libvirt。设置为libvirt可以提高性能，但是导致vlan tagging, multicast等功能失效。需要内核版本3.17+</td>
</tr>
<tr>
<td>domain</td>
<td>DHCP相关配置：<br /><strong>name</strong><br />定义DHCP服务器的DNS Domain（域名）<br /><strong>localOnly</strong><br />如果设置为yes，则name对应子域名的解析均由VLAN自己的DNS服务器负责，与宿主机的DNS无关；如果设置为no，则无法解析的DNS请求转发给宿主机DNS处理</td>
</tr>
<tr>
<td>forward</td>
<td>
<p>通过配置此元素，可以让VLAN连接到物理网络。如果不指定此子元素，则VLAN与其它网络隔离（isolated mode）<br /><strong>mode</strong><br />定义包转发方式：</p>
<p style="padding-left: 30px;">nat  所有连接到此VLAN的客户机、物理网络之间的流量，均forward到宿主机的IP路由栈。从宿主机外部看来，所有客户机均使用宿主机的IP地址。这种mode适合多个客户机需要访问物理网络，而宿主机仅仅允许用于一个公共IP地址的应用场景。如果网络分配的IPv6地址，那么IPv6流量通过plain路由转发，因为IPv6没有NAT的概念。同一VLAN上地址相互通信，不进行NAT</p>
<p style="padding-left: 30px;">route  来自客户机的流量forward到宿主机的IP路由栈，但是不进行NAT。要使用这种方式，LAN路由器必须包含适当的路由表项，将流量返回给宿主机（进而转发给对应客户机）。使用此方式，则客户机的入站/出站会话不受限制</p>
<p style="padding-left: 30px;">open  类似于route，但是libvirt不会在宿主机上应用任何Firewall规则，也不支持设置dev属性</p>
<p style="padding-left: 30px;">bridge 桥接到既有网桥，该模式下libvirt不去创建网桥设备：</p>
<p style="padding-left: 60px;">当配置了<pre class="crayon-plain-tag">&lt;bridge name='br'/&gt;</pre> 时：桥接到一个宿主机既有（非libvirt管理）的网桥<br />当配置了<pre class="crayon-plain-tag">&lt;virtualport type='openvswitch'/&gt;</pre>  时：桥接到一个既有的Open vSwitch网桥<br />当配置了<pre class="crayon-plain-tag">&lt;interface dev='eth0' /&gt;</pre> 时：通过macvtap的桥接模式，直连到物理网络</p>
<p><strong>dev</strong><br />如果设置此属性，当mode=nat|route时，Firewall规则将限定仅仅forward到dev设备上，否则应用到所有设备。当设置为wlan0时生成的iptables规则：</p>
<pre class="crayon-plain-tag"># sudo iptables -t nat -L -nv

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in  out     source         destination         
    0     0 MASQUERADE  tcp  --  *   wlan0   10.0.0.0/16   !10.0.0.0/16   masq ports: 1024-65535
    0     0 MASQUERADE  udp  --  *   wlan0   10.0.0.0/16   !10.0.0.0/16   masq ports: 1024-65535
    0     0 MASQUERADE  all  --  *   wlan0   10.0.0.0/16   !10.0.0.0/16</pre>
<p>如果不设置dev，默认值为*，这就意味着：<span style="background-color: #c0c0c0;">从虚拟机中访问任何非10.0.0.0/16网段时，不管路由出口是哪个网卡</span>，封包到达宿主机后，会强制进行SNAT —— 哪怕目标地址在另外一台虚拟机上。这会让Flannel的host-gw模式失效，因为，Flannel的容器网络CIDR肯定不是10.0.0.0/16，这意味着跨节点（虚拟机）的CNI通讯会发生NAT，这显然是不期望发生的。 </p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag">&lt;!-- 桥接到物理网络，基于macvtap --&gt;
&lt;network connections='1'&gt;
  &lt;name&gt;default&lt;/name&gt;
  &lt;uuid&gt;519cbf63-8ec0-4893-ba9c-0747430bdecd&lt;/uuid&gt;
  &lt;forward dev='eth0' mode='bridge'&gt;
    &lt;interface dev='eth0' connections='1'/&gt;
  &lt;/forward&gt;
&lt;/network&gt;

&lt;!-- NAT转发，创建VLAN --&gt;
&lt;network connections='1'&gt;
  &lt;name&gt;default&lt;/name&gt;
  &lt;uuid&gt;9bae4de8-ca58-48c5-ba58-109aebf8b954&lt;/uuid&gt;
  &lt;forward mode='nat' dev="wlan0"&gt;
    &lt;nat&gt;
      &lt;port start='1024' end='65535'/&gt;
    &lt;/nat&gt;
  &lt;/forward&gt;
  &lt;bridge name='virbr0' stp='off' delay='0'/&gt;
  &lt;mac address='de:ad:be:ef:00:00'/&gt;
  &lt;ip address='10.0.0.1' netmask='255.0.0.0'&gt;
    &lt;dhcp&gt;
      &lt;range start='10.0.0.100' end='10.0.0.200'/&gt;
      &lt;host mac='DE:AD:BE:EF:00:02' ip='10.0.0.2'/&gt;
    &lt;/dhcp&gt;
  &lt;/ip&gt;
&lt;/network&gt;</pre>
</td>
</tr>
<tr>
<td>bandwidth</td>
<td>
<p>配置虚拟网络的QoS，仅支持fowward mode=route|nat或者隔离网络。配置示例：
<pre class="crayon-plain-tag">&lt;bandwidth&gt;
    &lt;inbound average='1000' peak='5000' burst='5120'/&gt;
    &lt;outbound average='128' peak='256' burst='256'/&gt;
&lt;/bandwidth&gt;</pre>
</td>
</tr>
<tr>
<td>ip</td>
<td>
<p>设置虚拟局域网的子网、网桥（DHCP服务器）的IP地址、DHCP和DNS配置
<p><strong>ip子元素</strong></p>
<p>该元素设置子网、网桥地址：</p>
<pre class="crayon-plain-tag">&lt;!-- 子网10.0.0.0/8，网桥地址10.0.0.1 --&gt;
&lt;ip address='10.0.0.1' netmask='255.0.0.0'&gt;</pre>
<p><strong>ip/dhcp子元素</strong></p>
<p>设置DHCP自动分配的地址范围、静态映射MAC地址到IP： </p>
<pre class="crayon-plain-tag">&lt;dhcp&gt;
    &lt;!-- 自动分配的地址范围 --&gt;
    &lt;range start='10.0.0.100' end='10.0.0.200'/&gt;
    &lt;!-- 对于给定的MAC地址，总是绑定到某个IP、机器网络名 --&gt;
    &lt;host mac="DE:AD:BE:EF:F1:10" name="fedora-10" ip="10.0.0.10" /&gt;
&lt;/dhcp&gt;</pre>
</td>
</tr>
<tr>
<td>dns</td>
<td>
<p>配置虚拟网络的DNS服务器，示例：
<pre class="crayon-plain-tag">&lt;!-- 如果enable为no，则libvirt不会启动DNS服务 --&gt;
&lt;dns enable="yes"&gt;
    &lt;!--
        每个forwarder元素定义一个备选DNS服务器：
        如果指定domain属性，则仅针对该domain下的子域名的查询请求转发给addr对应的DNS服务器
    --&gt;
    &lt;forwarder addr="8.8.8.8"/&gt;
    &lt;forwarder domain='example.com' addr="8.8.4.4"/&gt;
    &lt;forwarder domain='www.example.com'/&gt;
    &lt;!-- 静态DNS映射 --&gt;
    &lt;host ip='10.0.0.10'&gt;
        &lt;hostname&gt;fedora-10&lt;/hostname&gt;
        &lt;hostname&gt;fedora-10.local&lt;/hostname&gt;
    &lt;/host&gt;
&lt;/dns&gt;</pre>
</td>
</tr>
<tr>
<td>mac</td>
<td>
<p> 设置网桥的MAC地址，示例：
<p><pre class="crayon-plain-tag">&lt;mac address='DE:AD:BE:EF:F1:00'/&gt;</pre>
</td>
</tr>
</tbody>
</table>
<p>当你创建一个新的虚拟网络后，libvrit会在<pre class="crayon-plain-tag">/etc/libvirt/qemu/networks</pre> 目录中生成一个XML文件，该文件基于virsh net-define指定的那个配置文件。</p>
<div class="blog_h3"><span class="graybg">配置示例</span></div>
<p>基于NAT转发，启用DHCP，静态映射MAC到IP地址的例子：</p>
<pre class="crayon-plain-tag">&lt;network&gt;
  &lt;name&gt;default&lt;/name&gt;
  &lt;forward mode='nat'&gt;
  &lt;/forward&gt;
  &lt;bridge name='virbr0' stp='off' delay='0'/&gt;
  &lt;mac address='DE:AD:BE:EF:F1:00'/&gt;
  &lt;ip address='10.0.0.1' netmask='255.0.0.0'&gt;
    &lt;dhcp&gt;
      &lt;range start='10.0.0.100' end='10.0.0.200'/&gt;
      &lt;host mac="DE:AD:BE:EF:F1:10" ip="10.0.0.10" /&gt;
      &lt;host mac="DE:AD:BE:EF:F1:11" ip="10.0.0.11" /&gt;
    &lt;/dhcp&gt;
  &lt;/ip&gt;
&lt;/network&gt;</pre>
<p>使用宿主机既有网桥的例子： </p>
<pre class="crayon-plain-tag">&lt;network&gt;
    &lt;name&gt;default&lt;/name&gt;
    &lt;forward mode="bridge"/&gt;
    &lt;!-- br1是宿主机上既有的网桥，它可能直接连接到物理网络 --&gt;
    &lt;bridge name="br1"/&gt;
&lt;/network&gt;</pre>
<p>macvtap直连，需要 2.6.34+内核、仅支持QEMU/KVM。通过macvtap可以通过一组物理网络接口（不需要网桥）之一，直接连接到物理网络。客户机将直接具有物理网络的子网IP地址，并且与物理网络中其它机器互联互通。示例：</p>
<pre class="crayon-plain-tag">&lt;network&gt;
    &lt;name&gt;direct-macvtap&lt;/name&gt;
    &lt;forward mode="bridge"&gt;
        &lt;interface dev="eth0"/&gt;
        &lt;interface dev="eth1"/&gt;
    &lt;/forward&gt;
&lt;/network&gt;</pre>
<div class="blog_h1"><span class="graybg">存储池管理</span></div>
<p>通过存储池，libvirt集中管理物理机器上的存储资源，供客户机使用。存储池被划分为卷（volume），卷则可以作为块设备分配给客户机使用。</p>
<div class="blog_h2"><span class="graybg">池类型</span></div>
<p>libvirt支持以下类型的存储池后端：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">后端类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>目录</td>
<td>将宿主机的一个目录作为池看待，该目录中的文件可以包含各种客户机磁盘文件、镜像文件</td>
</tr>
<tr>
<td>本地文件系统</td>
<td>将宿主机上一个格式化好的文件系统作为池看待，文件系统类型可以是ext2,ext3,vfat </td>
</tr>
<tr>
<td>网络文件系统</td>
<td>使用远端网络文件系统服务器的导出目录作为存储池。默认为 NFS 网络文件系统</td>
</tr>
<tr>
<td>逻辑卷</td>
<td>使用已经创建好的 LVM 卷组，或者提供一系列生成卷组的源设备，libvirt 会在其上创建卷组，生成存储池</td>
</tr>
<tr>
<td>磁盘</td>
<td>使用磁盘作为存储池</td>
</tr>
<tr>
<td>iSCSI</td>
<td>使用 iSCSI 设备作为存储池</td>
</tr>
<tr>
<td>其它</td>
<td>SCSI、Multipath、RBD、Sheepdog、Gluster 、ZFS</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">NFS池</span></div>
<p>通常存储管理员负责维护存储设备，例如NFS服务器。而宿主机的管理员定义存储池，存储池包含了NFS shares到挂载目录的映射。当存储池启动时，libvirt会自动执行挂载操作。一旦存储池启动完毕，NFS share中的文件被当作卷看待，你可以在客户机的Domain配置文件中引用卷的路径，作为块设备的source。</p>
<p>使用NFS时，基于libvirt的API可以在池中创建/删除卷（即NFS上的文件），但是不能超过池的尺寸限制（NFS share有容量控制）。注意不是所有类型的池支持卷的创建/删除操作。 </p>
<div class="blog_h2"><span class="graybg">池XML配置</span></div>
<p>你可以通过XML文件来定义存储池，使用virsh命令可以定义、启动、停止、解除定义池，就像操作Domain、虚拟网络一样。你的配置文件被libvrit修改后，存放在<pre class="crayon-plain-tag">/etc/libvirt/storage/</pre> 目录中。配置示例：</p>
<pre class="crayon-plain-tag">&lt;!-- 基于本地目录的存储池 --&gt;
&lt;pool type="dir"&gt;
    &lt;name&gt;virtimages&lt;/name&gt;
    &lt;target&gt;
        &lt;path&gt;/var/lib/virt/images&lt;/path&gt;
    &lt;/target&gt;
&lt;/pool&gt;

&lt;!-- 基于iscsi的存储池 --&gt;
&lt;pool type="iscsi"&gt;
    &lt;name&gt;virtimages&lt;/name&gt;
    &lt;source&gt;
        &lt;host name="iscsi.example.com"/&gt;
        &lt;device path="iqn.2013-06.com.example:iscsi-pool"/&gt;
        &lt;auth type='chap' username='myuser'&gt;
            &lt;secret usage='libvirtiscsi'/&gt;
        &lt;/auth&gt;
    &lt;/source&gt;
    &lt;target&gt;
        &lt;path&gt;/dev/disk/by-path&lt;/path&gt;
    &lt;/target&gt;
&lt;/pool&gt;

&lt;!-- 基于本地文件系统的池 --&gt;
&lt;pool type="fs"&gt;
    &lt;name&gt;sda&lt;/name&gt;
    &lt;source&gt;
        &lt;!-- 使用的文件系统 --&gt;
    	&lt;device path="/dev/sda3"/&gt;
  	&lt;/source&gt;
    &lt;target&gt;
    	&lt;!-- 指定挂载点 --&gt;
        &lt;path&gt;/home/alex/Vmware/libvirt/images/sda&lt;/path&gt;
        &lt;permissions&gt;
            &lt;mode&gt;0777&lt;/mode&gt;
        &lt;/permissions&gt;
    &lt;/target&gt;
&lt;/pool&gt;</pre>
<div class="blog_h3"><span class="graybg">基本配置</span></div>
<p>一个存储池配置的根元素为pool，最基础的配置项如下：</p>
<pre class="crayon-plain-tag">&lt;pool type="iscsi"&gt;
    &lt;name&gt;virtimages&lt;/name&gt;
    &lt;uuid&gt;3e3fce45-4f53-4fa7-bb32-11f34168b82b&lt;/uuid&gt;
    &lt;allocation&gt;10000000&lt;/allocation&gt;
    &lt;capacity&gt;50000000&lt;/capacity&gt;
    &lt;available&gt;40000000&lt;/available&gt;
&lt;/pool&gt;</pre>
<p>属性和子元素说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性/子元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@type</td>
<td>存储池类型，支持取值dir, fs, netfs, disk, iscsi, logical, scsi,mpath,rbd,sheepdog,gluster,zfs</td>
</tr>
<tr>
<td>name</td>
<td>对于宿主机来说，唯一的名称</td>
</tr>
<tr>
<td>uuid</td>
<td>全局唯一的UUID，如果忽略libvirt会自动生成</td>
</tr>
<tr>
<td>allocation</td>
<td>当前分配给池的容量，单位字节。创建池时不能使用此元素</td>
</tr>
<tr>
<td>capacity</td>
<td>池的总容量，单位字节。创建池时不能使用此元素</td>
</tr>
<tr>
<td>available</td>
<td>设置可以分配给新的卷使用的空闲容量。创建池时不能使用此元素</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">source子元素</span></div>
<p>存储池具有最多一个（某些类型的池后端没有）source子元素，用于描述池的来源。source包含的内容取决于池的type。 配置示例：</p>
<pre class="crayon-plain-tag">&lt;!-- disk池配置 --&gt;
&lt;source&gt;
    &lt;device path='/dev/mapper/mpatha' part_separator='no'/&gt;
    &lt;format type='gpt'/&gt;
&lt;/source&gt;

&lt;!-- SCSI池配置 --&gt;
&lt;source&gt;
    &lt;adapter type='scsi_host' name='scsi_host1'/&gt;
&lt;/source&gt;
&lt;source&gt;
    &lt;adapter type='scsi_host'&gt;
        &lt;parentaddr unique_id='1'&gt;
            &lt;address domain='0x0000' bus='0x00' slot='0x1f' addr='0x2'/&gt;
        &lt;/parentaddr&gt;
    &lt;/adapter&gt;
&lt;/source&gt;</pre>
<p>各子元素说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">source的子元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>device</td>
<td>对于fs, logical, disk, iscsi, zfs类型的池后端，指定存储池的后端物理设备。对于某些池后端，该元素可能出现多次。支持以下属性：<br />path，指向物理设备的全限定路径，或者iSCSI全限定名称（IQN）<br />part_separator，yes/no，用于disk后端</td>
</tr>
<tr>
<td>dir</td>
<td>对于 dir, netfs, gluster类型的池后端，指定存储池对应的文件目录。仅能出现一次。支持以下属性：<br />path，目录的全限定名称。对于Samba share，路径不包含起始的斜杠</td>
</tr>
<tr>
<td>adapter</td>
<td>对于scsi类型的池后端，指定存储池使用的SCSI适配器</td>
</tr>
<tr>
<td>host</td>
<td>对于 netfs, iscsi, rbd, sheepdog, gluster类型的池后端，如果存储池位于远程机器上，指定远程机器的信息，必须和device或者dir元素联用。支持以下属性：<br />name  远程机器的IP或者主机名<br />port  监听端口，可选，默认值依据远程协议确定</td>
</tr>
<tr>
<td>auth</td>
<td>提供访问池后端所需的身份验证信息。支持以下属性：<br />type  可以是chap或者ceph，分别用于iscsi、rbd<br />username  用户名<br />secret  密码信息（libvirt secret object）</td>
</tr>
<tr>
<td>name</td>
<td>对于logical, rbd, sheepdog, gluster类型的池后端，依据已命名元素提供池的源</td>
</tr>
<tr>
<td>format</td>
<td>
<p>指定源的格式</p>
<p>对于fs池后端，指定的是文件系统的类型：auto  ext2  ext3  ext4  ufs  iso9660  udf  gfs  gfs2  vfat  hfs+  xfs  ocfs2</p>
<p>对于netfs池后端，指定的是网络协议的类型：auto  nfs  glusterfs  cifs</p>
<p>对于logical池后端，只能是lvm2</p>
<p>对于disk池后端，指定的是分区表类型：dos  dvh  gpt  mac  bsd  pc98  sun  lvm2</p>
</td>
</tr>
<tr>
<td>vendor</td>
<td>指定存储设备的供应商信息</td>
</tr>
<tr>
<td>product</td>
<td>指定存储设备的产品信息</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">target子元素</span></div>
<p>对于dir, fs, netfs, logical, disk, iscsi, scsi, mpath类型池后端，可以具有单个target子元素。该元素描述<span style="background-color: #c0c0c0;">如何映射存储池的source到宿主机的文件系统命名空间</span>。配置示例：</p>
<pre class="crayon-plain-tag">&lt;target&gt;
    &lt;path&gt;/dev/disk/by-path&lt;/path&gt;
    &lt;permissions&gt;
        &lt;owner&gt;107&lt;/owner&gt;
        &lt;group&gt;107&lt;/group&gt;
        &lt;mode&gt;0744&lt;/mode&gt;
        &lt;label&gt;virt_image_t&lt;/label&gt;
    &lt;/permissions&gt;
    &lt;timestamps&gt;
        &lt;atime&gt;1341933637.273190990&lt;/atime&gt;
        &lt;mtime&gt;1341930622.047245868&lt;/mtime&gt;
        &lt;ctime&gt;1341930622.047245868&lt;/ctime&gt;
    &lt;/timestamps&gt;
    &lt;encryption type='...'&gt;&lt;/encryption&gt;
&lt;/target&gt;</pre>
<p>各子元素说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">target的子元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>path</td>
<td>存储池映射到宿主机目录树的什么位置。对于：
<ol>
<li><span style="color: #333333; font-family: Ubuntu, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 22px;">fs,dir池后端，该路径是在其中创建卷的绝对路径</span></li>
<li>基于设备的后端，该路径是设备节点所在路径，最好使用稳定（重启后不变化）的路径：/dev/disk/by-{path|id|uuid|label}，避免重启后路径变化</li>
<li>mpath后端，设置该子元素无效，总是使用默认值/dev/mapper</li>
</ol>
</td>
</tr>
<tr>
<td>permissions</td>
<td>仅用于fs,dir池后端，指定池对应目录的文件模式</td>
</tr>
<tr>
<td>timestamps</td>
<td>提供卷的时间戳信息</td>
</tr>
<tr>
<td>encryption</td>
<td>指定卷的加密方式</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">卷XML配置</span></div>
<p>存储卷，通常对应一个文件或者设备节点。注意：当创建了存储池后，<span style="background-color: #c0c0c0;">池中的镜像文件会被libvirt自动识别</span>，并创建相应的卷定义。</p>
<p>配置示例：</p>
<pre class="crayon-plain-tag">&lt;!-- 基于文件的卷 --&gt;
&lt;volume type='file'&gt;
    &lt;name&gt;fedora-10.qcow2&lt;/name&gt;
    &lt;capacity unit='GiB'&gt;16&lt;/capacity&gt;
    &lt;target&gt;
        &lt;path&gt;/home/alex/Vmware/libvirt/images/fedora-10.qcow2&lt;/path&gt;
        &lt;format type='qcow2'/&gt;
        &lt;permissions&gt;
            &lt;mode&gt;0666&lt;/mode&gt;
        &lt;/permissions&gt;
    &lt;/target&gt;
    &lt;backingStore&gt;
        &lt;path&gt;/home/alex/Vmware/libvirt/images/fedora-base.qcow2&lt;/path&gt;
        &lt;format type='qcow2'/&gt;
        &lt;permissions&gt;
            &lt;mode&gt;0666&lt;/mode&gt;
        &lt;/permissions&gt;
    &lt;/backingStore&gt;
&lt;/volume&gt;
&lt;!-- 使用LUKS加密的卷--&gt;
&lt;volume&gt;
    &lt;name&gt;MyLuks.img&lt;/name&gt;
    &lt;capacity unit="G"&gt;5&lt;/capacity&gt;
    &lt;target&gt;
        &lt;path&gt;/var/lib/virt/images/MyLuks.img&lt;/path&gt;
        &lt;format type='raw'/&gt;
        &lt;encryption format='luks'&gt;
            &lt;secret type='passphrase' uuid='f52a81b2-424e-490c-823d-6bd4235bc572'/&gt;
        &lt;/encryption&gt;
    &lt;/target&gt;
&lt;/volume&gt;</pre>
<div class="blog_h3"><span class="graybg">基本配置</span></div>
<p>一个卷配置的根元素是volume，最基础的配置项如下：</p>
<pre class="crayon-plain-tag">&lt;volume type='file'&gt;
    &lt;name&gt;sparse.img&lt;/name&gt;
    &lt;key&gt;/var/lib/xen/images/sparse.img&lt;/key&gt;
    &lt;allocation&gt;0&lt;/allocation&gt;
    &lt;capacity unit="T"&gt;1&lt;/capacity&gt;
&lt;/volume&gt;</pre>
<p> 属性和子元素说明如下：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性/子元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@type</td>
<td>该属性仅能读取，显示卷的实际类型</td>
</tr>
<tr>
<td>name</td>
<td>存储池范围内唯一的卷名称</td>
</tr>
<tr>
<td>key</td>
<td>用于唯一性的识别卷，该子元素不能在创建卷的时候指定，它总是自动生成的</td>
</tr>
<tr>
<td>allocation</td>
<td>指定分配给卷的存储空间，如果卷是稀疏分配的，该值可以小与卷的逻辑大小。可用属性：<br />unit  计量单位</td>
</tr>
<tr>
<td>capacity</td>
<td>指定卷的逻辑大小。可用属性：<br />unit 计量单位</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">target子元素</span></div>
<p>volume包含一个target子元素，指定卷如何映射到宿主机的文件系统中：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">target的子元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>path</td>
<td>卷在本地文件系统的绝对路径。该元素为只读，在创建时不能指定，libvirt会根据卷的name元素自动生成此元素</td>
</tr>
<tr>
<td>format</td>
<td>
<p>指定卷的格式：</p>
<p>对于dir,fs,nfs池后端，支持的卷格式包括：<br />raw  原始文件<br />bochs  Bochs磁盘镜像格式<br />cloop   压缩的loopback磁盘镜像格式<br />cow   用户模式Linux磁盘镜像格式<br />dmg   MAC的磁盘镜像格式<br />iso    光盘镜像格式<br />qcow  QEMU v1磁盘镜像格式<br />qcow2  QEMU v2磁盘镜像格式<br />qed  QEMU增强磁盘镜像格式<br />vmdk  Vmware磁盘镜像格式<br />vpc    VirtualPC磁盘镜像格式</p>
<p>对于disk池后端，支持的卷格式包括：<br />none  linux  fat16  fat32 linux-swap  linux-lvm  linux-raid  extended</p>
<p>对于logical,iscsi,scsi,mpath,rbd,sheepdog,gluster,zfs池后端，不支持该元素</p>
</td>
</tr>
<tr>
<td>permissions</td>
<td>指定卷的文件模式</td>
</tr>
<tr>
<td>compat</td>
<td>兼容性级别，仅仅用于qcow2格式的卷</td>
</tr>
<tr>
<td>nocow</td>
<td>关闭COW，仅仅支持btrfs文件系统中的、文件形式的卷</td>
</tr>
<tr>
<td>features</td>
<td>特定于format的配置项</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">backingStore子元素</span></div>
<p>volume可能包含一个backingStore子元素，用来描述卷的copy-on-write后端存储，配置示例：</p>
<pre class="crayon-plain-tag">&lt;backingStore&gt;
    &lt;path&gt;/var/lib/virt/images/master.img&lt;/path&gt;
    &lt;format type='raw'/&gt;
    &lt;permissions&gt;
        &lt;owner&gt;107&lt;/owner&gt;
        &lt;group&gt;107&lt;/group&gt;
        &lt;mode&gt;0744&lt;/mode&gt;
        &lt;label&gt;virt_image_t&lt;/label&gt;
    &lt;/permissions&gt;
&lt;/backingStore&gt; </pre>
<div class="blog_h1"><span class="graybg">迁移</span></div>
<p>在不同宿主机之间迁移客户机是比较复杂的问题，为了应对不同的需求，libvirt实现了多种解决方案。</p>
<div class="blog_h2"><span class="graybg">网络数据传输</span></div>
<p>从数据传输的角度来看，libvirt支持两种类型的迁移：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">传输类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>native</td>
<td>
<p>使用Hypervisor本身的数据传输机制，直接在源、目标宿主机之前进行迁移。该方式可能不支持加密，但是需要最少的数据传输量</p>
<p>可能需要针对Hypervisor进行手工的网络相关的配置，某些Hypervisor需要打开大量的端口才能支持并发的迁移操作</p>
</td>
</tr>
<tr>
<td>tunnelled</td>
<td>
<p>利用libvirt内置此RPC通信协议进行数据传输，总是支持数据加密。数据先从源客户机拷贝到源宿主机上的libvirt，再发送到目标主机的libvrit，最终发送给目标客户机，因此需要额外的数据传输量</p>
<p>不需要针对Hypervisor进行网络配置，仅仅需要开放一个端口</p>
<p>如果客户机内存很大，因而脏页变化频繁，额外的数据传输可能导致性能问题</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">通信控制路径</span></div>
<p>从通信控制路径角度来看，libvirt支持三种类型的迁移：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">通信控制路径</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>受管直接迁移</td>
<td>
<p>由libvirt客户端进程控制迁移的各个阶段。libvirt客户端必须能够连接到源、目的客户机的libvirt守护进程，并通过身份验证。源、目的客户机上的libvirt守护进程不需要相互通信</p>
<p>如果libvirt客户端崩溃，或者丢失到libvirt守护进程的连接，则源宿主机上的迁移会取消，并在源宿主机上重启客户机的CPU</p>
</td>
</tr>
<tr>
<td>受管点对点迁移</td>
<td>
<p>libvirt客户端仅仅与源宿主机上的libvirt守护进程通信，由后者控制迁移的整个过程。源宿主机的libvirt守护进程会连接到目的宿主机的libvirt守护进程执行迁移操作，libvirt客户端崩溃或者断开不会影响迁移过程的推进</p>
<p>注意，源宿主机上的libvirt守护进程使用自己的身份（通常是root）而不是客户端的身份连接到目标宿主机</p>
</td>
</tr>
<tr>
<td>非受管直接迁移</td>
<td>
<p>libvirt客户端、守护进程都不控制迁移过程，迁移由底层的Hypervisor负责。libvirt仅仅在Hypervisor的管理层次上触发迁移请求</p>
<p>即使libvirt客户端、守护进程都崩溃，迁移过程还会继续推进</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">数据安全的考虑</span></div>
<p>迁移时，客户机的内存需要完整的在网络上传输，这可能导致客户机中的敏感信息被嗅探。</p>
<p>如果宿主机有多个网络接口，或者交换机支持Tagged虚拟局域网，最好将客户机的网络流量和迁移/管理网络流量隔离开来。</p>
<p>某些场景下，仅仅依靠网络隔离不能满足安全性需要，这时，需要使用libvirt的tunnelled传输，启用数据加密。</p>
<div class="blog_h2"><span class="graybg">离线迁移</span></div>
<p>所谓离线迁移，就是把源主机的Domain状态设置为inactive，并且在迁移完毕后，Domain在源主机上继续运行，在目标主机上保持inactive状态。目前离线迁移不支持拷贝非共享存储。</p>
<div class="blog_h1"><span class="graybg">PCI直通</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>开发虚拟机固件（Open Virtual Machine Firmware）是一个为虚拟机提供UEFI支持的项目。从Linux 3.9开始，配合最近版本的QEMU，能够支持PCI设备直通，例如将显卡直接分配给虚拟机，这样虚拟机就获得接近原生显卡的性能。</p>
<p>对于Intel CPU，除了CPU需要支持VT-x之外，还需要支持VT-d（Intel Virtualization Technology for Directed I/O ）。此技术改善系统的安全性、可靠性，同时支持提升虚拟化环境下的IO性能。</p>
<p>Intel的VT-d和AMD的AMD-Vi，通称IOMMU。IOMMU特性不但要求CPU支持，主板、BIOS也需要配合。<span style="background-color: #c0c0c0;">开启IOMMU能够实现PCI直通、内存保护（免于被恶意、缺陷设备错误修改）</span>。</p>
<div class="blog_h2"><span class="graybg">启用IOMMU</span></div>
<div class="blog_h3"><span class="graybg">内核参数</span></div>
<pre class="crayon-plain-tag">intel_iommu=on iommu=pt</pre>
<p>设置iommu=pt能够防止内核触碰不能直通的设备。</p>
<p>重启后，使用下面的命令确认IOMMU已经正确启用： </p>
<pre class="crayon-plain-tag">dmesg | grep -i -e DMAR -e IOMMU
[    0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz-5.8.0-45-generic root=UUID=66acedc4-69d6-48b2-888c-9516089e004a ro quiet splash net.ifnames=0 biosdevname=0 intel_iommu=on iommu=pt vt.handoff=7
[    0.010062] ACPI: DMAR 0x0000000073AE3000 0000C8 (v01 INTEL  CML      00000002 INTL 01000013)
[    0.223797] Kernel command line: BOOT_IMAGE=/boot/vmlinuz-5.8.0-45-generic root=UUID=66acedc4-69d6-48b2-888c-9516089e004a ro quiet splash net.ifnames=0 biosdevname=0 intel_iommu=on iommu=pt vt.handoff=7
# IOMMU被启用
[    0.223929] DMAR: IOMMU enabled
[    0.546449] DMAR: Host address width 39
[    0.546450] DMAR: DRHD base: 0x000000fed90000 flags: 0x0
[    0.546455] DMAR: dmar0: reg_base_addr fed90000 ver 1:0 cap 1c0000c40660462 ecap 19e2ff0505e
[    0.546456] DMAR: DRHD base: 0x000000fed91000 flags: 0x1
[    0.546459] DMAR: dmar1: reg_base_addr fed91000 ver 1:0 cap d2008c40660462 ecap f050da
[    0.546460] DMAR: RMRR base: 0x0000007372c000 end: 0x0000007374bfff
[    0.546461] DMAR: RMRR base: 0x00000078000000 end: 0x0000007c7fffff
[    0.546462] DMAR: RMRR base: 0x00000073792000 end: 0x00000073811fff
[    0.546464] DMAR-IR: IOAPIC id 2 under DRHD base  0xfed91000 IOMMU 1
[    0.546465] DMAR-IR: HPET id 0 under DRHD base 0xfed91000
[    0.546465] DMAR-IR: Queued invalidation will be enabled to support x2apic and Intr-remapping.
[    0.549640] DMAR-IR: Enabled IRQ remapping in x2apic mode
[    4.330593] iommu: Default domain type: Passthrough (set via kernel command line)
[    4.510270] DMAR: No ATSR found
[    4.510351] DMAR: dmar0: Using Queued invalidation
[    4.510354] DMAR: dmar1: Using Queued invalidation
# PCI设备被加入到的分组信息
[    4.510868] pci 0000:00:00.0: Adding to iommu group 0
[    4.510888] pci 0000:00:01.0: Adding to iommu group 1
[    4.510901] pci 0000:00:02.0: Adding to iommu group 2
[    4.510911] pci 0000:00:04.0: Adding to iommu group 3
[    4.510928] pci 0000:00:12.0: Adding to iommu group 4
[    4.510950] pci 0000:00:14.0: Adding to iommu group 5
[    4.510963] pci 0000:00:14.2: Adding to iommu group 5
[    4.510975] pci 0000:00:14.3: Adding to iommu group 6
[    4.510997] pci 0000:00:15.0: Adding to iommu group 7
[    4.511008] pci 0000:00:15.1: Adding to iommu group 7
[    4.511031] pci 0000:00:16.0: Adding to iommu group 8
[    4.511042] pci 0000:00:16.3: Adding to iommu group 8
[    4.511087] pci 0000:00:1b.0: Adding to iommu group 9
[    4.511121] pci 0000:00:1b.4: Adding to iommu group 9
[    4.511161] pci 0000:00:1c.0: Adding to iommu group 10
[    4.511197] pci 0000:00:1c.5: Adding to iommu group 10
[    4.511226] pci 0000:00:1d.0: Adding to iommu group 11
[    4.511265] pci 0000:00:1f.0: Adding to iommu group 12
[    4.511277] pci 0000:00:1f.3: Adding to iommu group 12
[    4.511290] pci 0000:00:1f.4: Adding to iommu group 12
[    4.511301] pci 0000:00:1f.5: Adding to iommu group 12
[    4.511316] pci 0000:00:1f.6: Adding to iommu group 12
[    4.511321] pci 0000:01:00.0: Adding to iommu group 1
[    4.511325] pci 0000:01:00.1: Adding to iommu group 1
[    4.511330] pci 0000:01:00.2: Adding to iommu group 1
[    4.511335] pci 0000:01:00.3: Adding to iommu group 1
[    4.511350] pci 0000:02:00.0: Adding to iommu group 9
[    4.511363] pci 0000:03:00.0: Adding to iommu group 9
[    4.511373] pci 0000:04:00.0: Adding to iommu group 10
[    4.511383] pci 0000:05:00.0: Adding to iommu group 10
[    4.511392] pci 0000:05:01.0: Adding to iommu group 10
[    4.511399] pci 0000:05:02.0: Adding to iommu group 10
[    4.511409] pci 0000:05:04.0: Adding to iommu group 10
[    4.511435] pci 0000:06:00.0: Adding to iommu group 10
[    4.511458] pci 0000:3a:00.0: Adding to iommu group 10
[    4.511471] pci 0000:6e:00.0: Adding to iommu group 10
[    4.511484] pci 0000:6f:00.0: Adding to iommu group 11
# VT-d被启用
[    4.511850] DMAR: Intel(R) Virtualization Technology for Directed I/O
[    4.711749]     intel_iommu=on</pre>
<div class="blog_h3"><span class="graybg">PCI设备分组</span></div>
<p>使用下面的脚本可以更加清楚的看到PCI设备是如何并映射到IOMMU分组的：</p>
<pre class="crayon-plain-tag">#!/bin/bash
shopt -s nullglob
for g in `find /sys/kernel/iommu_groups/* -maxdepth 0 -type d | sort -V`; do
    echo "IOMMU Group ${g##*/}:"
    for d in $g/devices/*; do
        echo -e "\t$(lspci -nns ${d##*/})"
    done;
done;</pre>
<p>输出如下： </p>
<pre class="crayon-plain-tag">IOMMU Group 0:
	00:00.0 Host bridge [0600]: Intel Corporation Device [8086:9b44] (rev 02)
# Nvidia显卡被分配在组1
IOMMU Group 1:
	00:01.0 PCI bridge [0604]: Intel Corporation Xeon E3-1200 v5/E3-1500 v5/6th Gen Core Processor PCIe Controller (x16) [8086:1901] (rev 02)
	01:00.0 VGA compatible controller [0300]: NVIDIA Corporation TU104GLM [Quadro RTX 5000 Mobile / Max-Q] [10de:1eb5] (rev a1)
	01:00.1 Audio device [0403]: NVIDIA Corporation TU104 HD Audio Controller [10de:10f8] (rev a1)
	01:00.2 USB controller [0c03]: NVIDIA Corporation TU104 USB 3.1 Host Controller [10de:1ad8] (rev a1)
	01:00.3 Serial bus controller [0c80]: NVIDIA Corporation TU104 USB Type-C UCSI Controller [10de:1ad9] (rev a1)
# Intel集成显卡被分配在组2
IOMMU Group 2:
    #                                                                  设备ID
	00:02.0 VGA compatible controller [0300]: Intel Corporation Device [8086:9bf6] (rev 05)
IOMMU Group 3:
	00:04.0 Signal processing controller [1180]: Intel Corporation Xeon E3-1200 v5/E3-1500 v5/6th Gen Core Processor Thermal Subsystem [8086:1903] (rev 02)
IOMMU Group 4:
	00:12.0 Signal processing controller [1180]: Intel Corporation Comet Lake PCH Thermal Controller [8086:06f9]
IOMMU Group 5:
	00:14.0 USB controller [0c03]: Intel Corporation Comet Lake USB 3.1 xHCI Host Controller [8086:06ed]
	00:14.2 RAM memory [0500]: Intel Corporation Comet Lake PCH Shared SRAM [8086:06ef]
# 无线网卡被分到组6
IOMMU Group 6:
	00:14.3 Network controller [0280]: Intel Corporation Wi-Fi 6 AX201 [8086:06f0]
IOMMU Group 7:
	00:15.0 Serial bus controller [0c80]: Intel Corporation Comet Lake PCH Serial IO I2C Controller #0 [8086:06e8]
	00:15.1 Serial bus controller [0c80]: Intel Corporation Comet Lake PCH Serial IO I2C Controller #1 [8086:06e9]
IOMMU Group 8:
	00:16.0 Communication controller [0780]: Intel Corporation Comet Lake HECI Controller [8086:06e0]
	00:16.3 Serial controller [0700]: Intel Corporation Device [8086:06e3]
# 指纹识别器、三星硬盘被分到组9
IOMMU Group 9:
	00:1b.0 PCI bridge [0604]: Intel Corporation Comet Lake PCI Express Root Port #17 [8086:06c0] (rev f0)
	00:1b.4 PCI bridge [0604]: Intel Corporation Comet Lake PCI Express Root Port #21 [8086:06ac] (rev f0)
	02:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd NVMe SSD Controller SM981/PM981/PM983 [144d:a808]
	03:00.0 Non-Volatile memory controller [0108]: Silicon Motion, Inc. Device [126f:2262] (rev 03)
# 雷电、读卡器被分到组10
IOMMU Group 10:
	00:1c.0 PCI bridge [0604]: Intel Corporation Device [8086:06b8] (rev f0)
	00:1c.5 PCI bridge [0604]: Intel Corporation Device [8086:06bd] (rev f0)
	04:00.0 PCI bridge [0604]: Intel Corporation JHL7540 Thunderbolt 3 Bridge [Titan Ridge 4C 2018] [8086:15ea] (rev 06)
	05:00.0 PCI bridge [0604]: Intel Corporation JHL7540 Thunderbolt 3 Bridge [Titan Ridge 4C 2018] [8086:15ea] (rev 06)
	05:01.0 PCI bridge [0604]: Intel Corporation JHL7540 Thunderbolt 3 Bridge [Titan Ridge 4C 2018] [8086:15ea] (rev 06)
	05:02.0 PCI bridge [0604]: Intel Corporation JHL7540 Thunderbolt 3 Bridge [Titan Ridge 4C 2018] [8086:15ea] (rev 06)
	05:04.0 PCI bridge [0604]: Intel Corporation JHL7540 Thunderbolt 3 Bridge [Titan Ridge 4C 2018] [8086:15ea] (rev 06)
	06:00.0 System peripheral [0880]: Intel Corporation JHL7540 Thunderbolt 3 NHI [Titan Ridge 4C 2018] [8086:15eb] (rev 06)
	3a:00.0 USB controller [0c03]: Intel Corporation JHL7540 Thunderbolt 3 USB Controller [Titan Ridge 4C 2018] [8086:15ec] (rev 06)
	6e:00.0 Unassigned class [ff00]: Realtek Semiconductor Co., Ltd. RTS525A PCI Express Card Reader [10ec:525a] (rev 01)
IOMMU Group 11:
	00:1d.0 PCI bridge [0604]: Intel Corporation Comet Lake PCI Express Root Port #9 [8086:06b0] (rev f0)
	6f:00.0 Non-Volatile memory controller [0108]: Silicon Motion, Inc. Device [126f:2262] (rev 03)
# 有线网卡被分到组12
IOMMU Group 12:
	00:1f.0 ISA bridge [0601]: Intel Corporation Device [8086:068e]
	00:1f.3 Multimedia audio controller [0401]: Intel Corporation Comet Lake PCH cAVS [8086:06c8]
	00:1f.4 SMBus [0c05]: Intel Corporation Comet Lake PCH SMBus Controller [8086:06a3]
	00:1f.5 Serial bus controller [0c80]: Intel Corporation Comet Lake PCH SPI Controller [8086:06a4]
	00:1f.6 Ethernet controller [0200]: Intel Corporation Ethernet Connection (10) I219-LM [8086:0d4e] </pre>
<p>需要注意，<span style="background-color: #c0c0c0;">IOMMU组是直通的最小调度单元</span>。也就是说，你可能被迫将多个设备一起直通给虚拟机，或者将设备放到其他PCI插槽。</p>
<div class="blog_h2"><span class="graybg">隔离PCI设备</span></div>
<p>为了将设备分配给虚拟机，它和它所在分组的所有其他设备的驱动，<span style="background-color: #c0c0c0;">必须更换为一个占位符驱动（stub 或 VFIO驱动）</span>。这样<span style="background-color: #c0c0c0;">，宿主机就不会和该设备进行交互</span>。大部分设备，可以在启动虚拟机之前，即席的完成驱动更换操作。</p>
<p>对于显卡，没有这么方便。GPU驱动通常对动态rebinding支持的不好，不能将宿主机上正在使用的GPU透明的直通给虚拟机。因此，我们需要<span style="background-color: #c0c0c0;">在宿主机启动时，尽早将占位符驱动bind给GPU</span>。这样，<span style="background-color: #c0c0c0;">除非驱动被换回，或者虚拟机claim该GPU，它会保持inactive状态</span>。</p>
<p>之所以要求尽早，是因为Linux现有的驱动机制是<span style="background-color: #c0c0c0;">每个驱动在初始化时，自行去寻找没有初始化的，自己关注的硬件。因此驱动的初始化顺序非常关键</span>。如果慢了，物理驱动就会抢先绑定设备。</p>
<p>从内核4.1开始，<span style="background-color: #c0c0c0;">VFIO驱动vfio-pci被包含其中</span>。可以用来代替pci-stub。</p>
<div class="blog_h3"><span class="graybg">基于pci-stub</span></div>
<p>修改内核参数：</p>
<pre class="crayon-plain-tag">pci-stub.ids=8086:9bf6</pre>
<p>pci-stub根据ID来识别设备，也就是说，我们需要提供用于直通的PCI设备的ID。对于上面的输出中，Intel显卡的ID是8086:9bf6。</p>
<p>如果要绑定<span style="background-color: #c0c0c0;">多个设备，ID需要用逗号分隔</span>。 </p>
<p>随后我们需要执行下面的命令并重启：</p>
<pre class="crayon-plain-tag">sudo update-grub</pre>
<div class="blog_h3"><span class="graybg">基于vfio-pci</span></div>
<p>内核总是先初始化所有内建的驱动，然后才逐个初始化外部模块。 在<span style="background-color: #c0c0c0;">外部模块</span>中，我们可以通过softdep来增加模块间的依赖关键： 例如我们可以把vfio-pci列为nvidia的依赖，那么vfio-pci总会被在nvidia之前初始化，而确保能抢占到硬件。</p>
<p>我们首先需要弄清楚，需要直通的硬件，当前是被什么驱动所绑定：</p>
<pre class="crayon-plain-tag">lspci -nnv

00:02.0 VGA compatible controller [0300]: Intel Corporation Device [8086:9bf6] (rev 05) (prog-if 00 [VGA controller])
	DeviceName: Onboard IGD
	Subsystem: Hewlett-Packard Company Device [103c:8780]
	Flags: bus master, fast devsel, latency 0, IRQ 221
	Memory at df000000 (64-bit, non-prefetchable) [size=16M]
	Memory at a0000000 (64-bit, prefetchable) [size=256M]
	I/O ports at 4000 [size=64]
	Expansion ROM at 000c0000 [virtual] [disabled] [size=128K]
	Capabilities: &lt;access denied&gt;
    # 当前使用的驱动
	Kernel driver in use: i915
	Kernel modules: i915</pre>
<p>i915是内核builtin的驱动，无法通过修改softdep，改变驱动加载顺序。解决此问题，有两种方案：</p>
<ol>
<li>将i915从内核剥离出来，编译为模块</li>
<li>将vfio-pci编译进内核</li>
</ol>
<p>如果选择方案2，则使用内核参数：<pre class="crayon-plain-tag">vfio-pci.ids=8086:9bf6</pre> 即可完成设备隔离。对于Ubuntu 20.04，vfio-pci已经默认编译到内核中。</p>
<div class="blog_h1"><span class="graybg">Hooks</span></div>
<p>libvirt提供钩子（Hooks）功能，你可以添加自定义的脚本，存放在/etc/libvirt/hooks目录下，以便在：</p>
<ol>
<li>libvirt守护程序启动、停止、reload时（对应daemon子目录）</li>
<li>QEMU客户机启动、停止时（对应qemu子目录）</li>
<li>一个虚拟网络启动、停止时，或者一个网络接口接入、断开网络时（对应network子目录）</li>
</ol>
<p>执行特定的逻辑。Hooks可以基于Bash或者Python编写。</p>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">Vmware中运行的libvirt</span></div>
<div class="blog_h3"><span class="graybg">无法使用macvtap直连的虚拟网络</span></div>
<p>报错：The virtual machine's operating system has attempted to enable promiscuous mode on adapter 'Ethernet0'.</p>
<p>要解决此问题，需要在宿主机上执行：</p>
<pre class="crayon-plain-tag"># vmnet0改为实际使用的Vmware虚拟网络
sudo chmod a+rw /dev/vmnet0</pre>
<div class="blog_h3"><span class="graybg">无法启动基于KVM的嵌套虚拟机</span></div>
<p>报错信息：error: unsupported configuration: Domain requires KVM, but it is not available. Check that virtualization is enabled in the host BIOS, and host configuration is setup to load the kvm modules.</p>
<p>解决办法：VM ⇨ Settings，弹出的对话框中，选择Processors，在面板右侧勾选Virtualize Intel VT-X/EPT or AMD-V/RVI</p>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">AppArmor导致无法启动QEMU虚拟机</span></div>
<p>报错信息：error: internal error: cannot load AppArmor profile 'libvirt-cf9a1d56-306d-4a6f-8b0e-79ac070aa8fe'</p>
<p>这个错误与安全模块AppArmor有关，你可以简单的禁用libvirt的QEMU驱动的安全驱动来规避：</p>
<pre class="crayon-plain-tag"># 添加
security_driver = "none"</pre>
<p>并执行：</p>
<pre class="crayon-plain-tag">sudo touch /etc/apparmor.d/disable/usr.sbin.libvirtd</pre>
<p>然后重启libvirtd：<pre class="crayon-plain-tag">sudo service libvirt-bin restart</pre>  </p>
<div class="blog_h3"><span class="graybg">无法启动桥接的QEMU虚拟机</span></div>
<p>报错信息：<br />error: internal error: process exited while connecting to monitor: failed to open /dev/net/tun: Operation not permitted<br />failed to launch bridge helper<br />qemu-system-x86_64: -netdev bridge,id=tap0,br=br0: Device 'bridge' could not be initialized</p>
<p>解决办法：</p>
<pre class="crayon-plain-tag"># 添加如下几行 

user = "root"
group = "root"
cgroup_device_acl = [
        "/dev/null", "/dev/full", "/dev/zero",
        "/dev/random", "/dev/urandom",
        "/dev/ptmx", "/dev/kvm", "/dev/kqemu",
        "/dev/rtc", "/dev/hpet", "/dev/net/tun"
]
clear_emulator_capabilities = 0</pre>
<div class="blog_h3"><span class="graybg">-daemonize选项导致启动QEMU虚拟机报错</span></div>
<p>报错信息：error: internal error: process exited while connecting to monitor</p>
<p>解决办法：从Domain配置文件中去掉<pre class="crayon-plain-tag">&lt;qemu:arg value='-daemonize'/&gt;</pre> 即可。</p>
<div class="blog_h3"><span class="graybg">Ubuntu 16.04 Server无法启动</span></div>
<p>症状：无法启动成功，配置SDL则可以启动，但是需要Headless的服务器。</p>
<p>解决办法：配置一个graphics，类型为VNC。</p>
<div class="blog_h3"><span class="graybg">显示相关报错：SDL无法初始化</span></div>
<p>这导致虚拟机无法启动，QEMU的SDL窗口不出现。</p>
<p>报错信息：error: internal error: process exited while connecting to monitor: Could not initialize SDL(No available video device) - exiting</p>
<p>解决办法：</p>
<ol>
<li>首先在宿主机执行<pre class="crayon-plain-tag">env | egrep "DISPLAY|XAUTH"</pre> ，获取两个环境变量的值</li>
<li>然后修改Domain配置文件，把type=sdl的graphics元素的display和xauth属性修改为上面命令输出的环境变量值：<br />
<pre class="crayon-plain-tag">&lt;graphics type='sdl' display=':0' xauth='/home/alex/.Xauthority'/&gt;</pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">找不到可启动硬盘</span></div>
<p>报错信息：</p>
<p>Booting from Hard Disk...<br />Boot failed: not abootable disk<br />No bootable device.</p>
<p>报错原因：可能是Domain配置文件搞错了镜像文件的格式。</p>
<p>解决办法：修改为正确的镜像格式，例如：<pre class="crayon-plain-tag">&lt;driver name='qemu' type='qcow2'/&gt;</pre> </p>
<div class="blog_h3"><span class="graybg">无法连接到客户机Console</span></div>
<p>报错信息：error: internal error: cannot find character device &lt;null&gt;</p>
<p>解决办法：可能是你的Domain配置中缺少Console和串口的配置，添加：</p>
<pre class="crayon-plain-tag">&lt;serial type='pty'&gt;
    &lt;target port='0'/&gt;
&lt;/serial&gt;
&lt;console type='pty'&gt;
    &lt;target type='serial' port='0'/&gt;
&lt;/console&gt;</pre>
<div class="blog_h3"><span class="graybg">如何退出客户机Console</span></div>
<p>要退出从virsh console进入的控制台，可以按组合键<pre class="crayon-plain-tag">Ctrl+]</pre> 。</p>
<div class="blog_h3"><span class="graybg">能连接到客户机Console但卡死</span></div>
<p>问题现象：光标在Escape character is ^]下面一行闪烁，但是按任何键都没有反应。</p>
<p>问题原因：客户机没有进行适当的配置。</p>
<p>解决办法： 通过其它方式登录到客户机，修改配置。</p>
<p><a id="console-to-ttys0"></a>以Fedora为例：</p>
<ol>
<li>修改/etc/sysconfig/grub（该文件是指向/etc/default/grub的符号链接）：<br />
<pre class="crayon-plain-tag"># 修改这一行，在命令行尾部添加console=...这个内核参数
GRUB_CMDLINE_LINUX="... console=ttyS0,115200"</pre>
</li>
<li>更新GRUB：<pre class="crayon-plain-tag">grub2-mkconfig &gt; /boot/grub2/grub.cfg</pre>  </li>
<li>执行命令<pre class="crayon-plain-tag">echo ttyS0 &gt;&gt; /etc/securetty</pre> 并重启客户机</li>
</ol>
<p>现在你可以使用virsh console连接到客户机，并执行Shell命令了。</p>
<div class="blog_h3"><span class="graybg">Console窗口太小</span></div>
<p>当通过串口（上面的方式）连接到Linux时，缺乏协商机制（Negotiate About Window Size ，NAWS），Linux也不知道你的（通过串口连接的）控制台屏幕屏幕大小。</p>
<p>在这种情况下，通常会使用<pre class="crayon-plain-tag">stty -a</pre>命令显示的结果，来设置窗口尺寸，往往不靠谱，偏小。</p>
<p>你可以在.bashrc中增加<pre class="crayon-plain-tag">/usr/bin/resize &gt; /dev/null</pre>，resize是来自xterm的工具，它可以使用VT100-style的光标位置来判断屏幕尺寸。resize会改变stty输出结果。</p>
<div class="blog_h3"><span class="graybg">无法删除外部快照</span></div>
<p>报错信息：unsupported configuration: deletion of 1 external disk snapshots not supported yet</p>
<p>解决办法：目前libvirt无法删除外部快照的磁盘文件，因此我们仅仅需要删除它的元数据，调用snapshot-delete时添加--metadata参数。磁盘文件可以手工删除。</p>
<div class="blog_h3"><span class="graybg">虚拟局域网（nat方式）无法连通</span></div>
<p>问题现象：无法ping通网桥，无法ping通外网，Vmware无法桥接到libvirt创建的网桥</p>
<p>原因分析：</p>
<p>在我的机器上，外网是通过Wifi连接的。如果设置虚拟局域网为net-autostart，则虚拟局域网启动时Wifi可能尚未连接，这会导致客户机无法连通外网的情况，原因未知。</p>
<p>将Vmware workstation中的虚拟机也桥接到virbr0时，提示无法连接。出现这种现象的原因也未知，但是先启动一个libvirt管理的客户机“激活”一下VLAN，然后启动Vmware就没事了。</p>
<p>解决办法：在Wifi连接后，启动VLAN、一台libvirt管理的虚拟机，然后再启动Vmware。</p>
<div class="blog_h3"><span class="graybg">使用QEMU驱动访问NFS报错：Permission denied</span></div>
<p>设置NFS导出时，要启用no_root_squash选项，让NFS客户端（QEMU宿主机）以root权限访问NFS：</p>
<pre class="crayon-plain-tag">/home/alex/Vmware/libvirt/images/default  10.0.0.0/8(rw,no_root_squash) </pre>
<p>然后，参考本文针对问题《AppArmor导致无法启动QEMU虚拟机》、 《无法启动桥接的QEMU虚拟机》的解决方案。</p>
<div class="blog_h3"><span class="graybg">启动或迁移时报错：Unable to get index for interface  ***: No such device</span></div>
<p>可能是因为虚拟局域网配置错误：</p>
<pre class="crayon-plain-tag">&lt;network&gt;
  &lt;name&gt;default&lt;/name&gt;
  &lt;forward mode="bridge"&gt;
    &lt;interface dev="eth0"/&gt; &lt;!-- 必须和宿主机的以太网设备名称一致 --&gt;
  &lt;/forward&gt;
&lt;/network&gt; </pre>
<div class="blog_h3"><span class="graybg">迁移时报错：Unsupported machine type</span></div>
<p>在Domain配置文件中指定宿主机集群中通用的机器类型，避免此问题：</p>
<pre class="crayon-plain-tag">&lt;os&gt;
    &lt;type arch='x86_64' machine='pc-i440fx-2.0'&gt;hvm&lt;/type&gt;
&lt;/os&gt;</pre>
<div class="blog_h3"><span class="graybg">迁移时报错，无法连接到49152端口</span></div>
<p>报错信息：</p>
<p style="padding-left: 30px;">unable to connect to server at 'Zircon:49152': Connection refused <br />unable to connect to server at 'centos.local:49152': No route to host</p>
<p>报错原因：默认情况下，迁移的源使用目的主机的主机名来建立迁移连接，出现此错误说明源无法正确连接到目标主机——Zircon，可以ping验证一下</p>
<p>解决方案：</p>
<ol>
<li>修改目的主机libvirt的QEMU配置：<br />
<pre class="crayon-plain-tag"># 指向目的主机可被迁移源访问的IP地址或者DNS名称
migration_host = "zircon.local"</pre>
</li>
<li>
<p> 老版本的libvirt可能不支持上述配置，这时可以配置DNS服务器或者迁移源的/etc/hosts文件</p>
</li>
<li>No route to host错误可能是因为目标主机的防火墙导致，注意检查设置</li>
</ol>
<div class="blog_h3"><span class="graybg">迁移时报错，Unable to read from monitor: Connection reset by peer</span></div>
<p>问题现象：迁移进度到达100%报如上错误。这个报错很笼统，详细的错误信息可以在目的宿主机的客户机运行日志（<pre class="crayon-plain-tag">/var/log/libvirt/qemu/$VM.log</pre>  ）中看到：</p>
<pre class="crayon-plain-tag">qemu: warning: error while loading state for instance 0x0 of device 'cpu'
load of migration failed
2016-10-19 01:47:45.913+0000: shutting down</pre>
<div class="blog_h3"><span class="graybg">启动后很快进入pause状态</span></div>
<p>可能是因为卷所在的物理磁盘空间不足。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/libvirt-study-note">libvirt学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/libvirt-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
