learn.lianglianglee.com/专栏/由浅入深吃透 Docker-完/04 容器操作:得心应手掌握 Docker 容器基本操作.md.html
2022-05-11 18:57:05 +08:00

967 lines
26 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>04 容器操作:得心应手掌握 Docker 容器基本操作.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/由浅入深吃透 Docker-完/00 溯本求源,吃透 Docker.md.html">00 溯本求源,吃透 Docker.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/01 Docker 安装:入门案例带你了解容器技术原理.md.html">01 Docker 安装:入门案例带你了解容器技术原理.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/02 核心概念:镜像、容器、仓库,彻底掌握 Docker 架构核心设计理念.md.html">02 核心概念:镜像、容器、仓库,彻底掌握 Docker 架构核心设计理念.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/03 镜像使用Docker 环境下如何配置你的镜像?.md.html">03 镜像使用Docker 环境下如何配置你的镜像?.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/由浅入深吃透 Docker-完/04 容器操作:得心应手掌握 Docker 容器基本操作.md.html">04 容器操作:得心应手掌握 Docker 容器基本操作.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/05 仓库访问:怎样搭建属于你的私有仓库?.md.html">05 仓库访问:怎样搭建属于你的私有仓库?.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/06 最佳实践:如何在生产中编写最优 Dockerfile.md.html">06 最佳实践:如何在生产中编写最优 Dockerfile.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/07 Docker 安全:基于内核的弱隔离系统如何保障安全性?.md.html">07 Docker 安全:基于内核的弱隔离系统如何保障安全性?.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/08 容器监控:容器监控原理及 cAdvisor 的安装与使用.md.html">08 容器监控:容器监控原理及 cAdvisor 的安装与使用.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/09 资源隔离:为什么构建容器需要 Namespace .md.html">09 资源隔离:为什么构建容器需要 Namespace .md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/10 资源限制:如何通过 Cgroups 机制实现资源限制?.md.html">10 资源限制:如何通过 Cgroups 机制实现资源限制?.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/11 组件组成:剖析 Docker 组件作用及其底层工作原理.md.html">11 组件组成:剖析 Docker 组件作用及其底层工作原理.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/12 网络模型:剖析 Docker 网络实现及 Libnetwork 底层原理.md.html">12 网络模型:剖析 Docker 网络实现及 Libnetwork 底层原理.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/13 数据存储:剖析 Docker 卷与持久化数据存储的底层原理.md.html">13 数据存储:剖析 Docker 卷与持久化数据存储的底层原理.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/14 文件存储驱动AUFS 文件系统原理及生产环境的最佳配置.md.html">14 文件存储驱动AUFS 文件系统原理及生产环境的最佳配置.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/15 文件存储驱动Devicemapper 文件系统原理及生产环境的最佳配置.md.html">15 文件存储驱动Devicemapper 文件系统原理及生产环境的最佳配置.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/16 文件存储驱动OverlayFS 文件系统原理及生产环境的最佳配置.md.html">16 文件存储驱动OverlayFS 文件系统原理及生产环境的最佳配置.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/17 原理实践:自己动手使用 Golang 开发 Docker.md.html">17 原理实践:自己动手使用 Golang 开发 Docker.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/18 原理实践:自己动手使用 Golang 开发 Docker.md.html">18 原理实践:自己动手使用 Golang 开发 Docker.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/19 如何使用 Docker Compose 解决开发环境的依赖?.md.html">19 如何使用 Docker Compose 解决开发环境的依赖?.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/20 如何在生产环境中使用 Docker Swarm 调度容器?.md.html">20 如何在生产环境中使用 Docker Swarm 调度容器?.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/21 如何使 Docker 和 Kubernetes 结合发挥容器的最大价值?.md.html">21 如何使 Docker 和 Kubernetes 结合发挥容器的最大价值?.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/22 多阶级构建Docker 下如何实现镜像多阶级构建?.md.html">22 多阶级构建Docker 下如何实现镜像多阶级构建?.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/23 DevOps容器化后如何通过 DevOps 提高协作效能?.md.html">23 DevOps容器化后如何通过 DevOps 提高协作效能?.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/24 CICD容器化后如何实现持续集成与交付.md.html">24 CICD容器化后如何实现持续集成与交付.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/25 CICD容器化后如何实现持续集成与交付.md.html">25 CICD容器化后如何实现持续集成与交付.md.html</a>
</li>
<li>
<a href="/专栏/由浅入深吃透 Docker-完/26 结束语 展望未来Docker 的称霸之路.md.html">26 结束语 展望未来Docker 的称霸之路.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>04 容器操作:得心应手掌握 Docker 容器基本操作</h1>
<p>前几天在咱们的社群里看到有同学在讨论,说面试的时候被问到容器和镜像的区别,有同学回答说没什么区别,也许是在开玩笑,不过这两者的区别很大。今天,我们就来看看容器的相关知识,比如什么是容器?容器的生命周期,以及容器常用的操作命令。学完之后你可以对比下与镜像的区别。</p>
<h3>容器Container是什么</h3>
<p>容器是基于镜像创建的可运行实例,并且单独存在,一个镜像可以创建出多个容器。运行容器化环境时,实际上是在容器内部创建该文件系统的读写副本。 这将添加一个容器层,该层允许修改镜像的整个副本。如图 1 所示。</p>
<p><img src="assets/CgqCHl9YmlSAGgF0AABXUH--rM4624.png" alt="image.png" /></p>
<p>图1 容器组成</p>
<p>了解完容器是什么,接下来我们聊一聊容器的生命周期。</p>
<h3>容器的生命周期</h3>
<p>容器的生命周期是容器可能处于的状态,容器的生命周期分为 5 种。</p>
<ol>
<li>created初建状态</li>
<li>running运行状态</li>
<li>stopped停止状态</li>
<li>paused 暂停状态</li>
<li>deleted删除状态</li>
</ol>
<p>各生命周期之前的转换关系如图所示:</p>
<p><img src="assets/CgqCHl9YmniARFcOAADHTlGkncs129.png" alt="image" /></p>
<p>图2 容器的生命周期</p>
<p>通过<code>docker create</code>命令生成的容器状态为初建状态,初建状态通过<code>docker start</code>命令可以转化为运行状态,运行状态的容器可以通过<code>docker stop</code>命令转化为停止状态,处于停止状态的容器可以通过<code>docker start</code>转化为运行状态,运行状态的容器也可以通过<code>docker pause</code>命令转化为暂停状态,处于暂停状态的容器可以通过<code>docker unpause</code>转化为运行状态 。处于初建状态、运行状态、停止状态、暂停状态的容器都可以直接删除。</p>
<p>下面我通过实际操作和命令来讲解容器各生命周期间的转换关系。</p>
<h3>容器的操作</h3>
<p>容器的操作可以分为五个步骤:创建并启动容器、终止容器、进入容器、删除容器、导入和导出容器。下面我们逐一来看。</p>
<h4>1创建并启动容器</h4>
<p>容器十分轻量,用户可以随时创建和删除它。我们可以使用<code>docker create</code>命令来创建容器,例如:</p>
<pre><code>$ docker create -it --name=busybox busybox
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
61c5ed1cbdf8: Pull complete
Digest: sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977
Status: Downloaded newer image for busybox:latest
2c2e919c2d6dad1f1712c65b3b8425ea656050bd5a0b4722f8b01526d5959ec6
$ docker ps -a| grep busybox
2c2e919c2d6d busybox &quot;sh&quot; 34 seconds ago Created busybox
</code></pre>
<p>如果使用<code>docker create</code>命令创建的容器处于停止状态,我们可以使用<code>docker start</code>命令来启动它,如下所示。</p>
<pre><code>$ docker start busybox
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d6f3d364fad3 busybox &quot;sh&quot; 16 seconds ago Up 8 seconds busybox
</code></pre>
<p>这时候我们可以看到容器已经处于启动状态了。
容器启动有两种方式:</p>
<ol>
<li>使用<code>docker start</code>命令基于已经创建好的容器直接启动 。</li>
<li>使用<code>docker run</code>命令直接基于镜像新建一个容器并启动,相当于先执行<code>docker create</code>命令从镜像创建容器,然后再执行<code>docker start</code>命令启动容器。</li>
</ol>
<p>使用<code>docker run</code>的命令如下:</p>
<pre><code>$ docker run -it --name=busybox busybox
</code></pre>
<p>当使用<code>docker run</code>创建并启动容器时Docker 后台执行的流程为:</p>
<ul>
<li>Docker 会检查本地是否存在 busybox 镜像,如果镜像不存在则从 Docker Hub 拉取 busybox 镜像;</li>
<li>使用 busybox 镜像创建并启动一个容器;</li>
<li>分配文件系统,并且在镜像只读层外创建一个读写层;</li>
<li>从 Docker IP 池中分配一个 IP 给容器;</li>
<li>执行用户的启动命令运行镜像。</li>
</ul>
<p>上述命令中, -t 参数的作用是分配一个伪终端,-i 参数则可以终端的 STDIN 打开,同时使用 -it 参数可以让我们进入交互模式。 在交互模式下,用户可以通过所创建的终端来输入命令,例如:</p>
<pre><code>$ ps aux
PID USER TIME COMMAND
1 root 0:00 sh
6 root 0:00 ps aux
</code></pre>
<p>我们可以看到容器的 1 号进程为 sh 命令,在容器内部并不能看到主机上的进程信息,因为容器内部和主机是完全隔离的。同时由于 sh 是 1 号进程,意味着如果通过 exit 退出 sh那么容器也会退出。所以对于容器来说<strong>杀死容器中的主进程,则容器也会被杀死。</strong></p>
<h4>2终止容器</h4>
<p>容器启动后,如果我们想停止运行中的容器,可以使用<code>docker stop</code>命令。命令格式为 docker stop [-t|--time[=10]]。该命令首先会向运行中的容器发送 SIGTERM 信号,如果容器内 1 号进程接受并能够处理 SIGTERM则等待 1 号进程处理完毕后退出,如果等待一段时间后,容器仍然没有退出,则会发送 SIGKILL 强制终止容器。</p>
<pre><code>$ docker stop busybox
busybox
</code></pre>
<p>如果你想查看停止状态的容器信息,你可以使用 docker ps -a 命令。</p>
<pre><code>$ docker ps -a
CONTAINERID IMAGE COMMAND CREATED STATUS PORTS NAMES
28d477d3737a busybox &quot;sh&quot; 26 minutes ago Exited (137) About a minute ago busybox
</code></pre>
<p>处于终止状态的容器也可以通过<code>docker start</code>命令来重新启动。</p>
<pre><code>$ docker start busybox
busybox
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
28d477d3737a busybox &quot;sh&quot; 30 minutes ago Up 25 seconds busybox
</code></pre>
<p>此外,<code>docker restart</code>命令会将一个运行中的容器终止,并且重新启动它。</p>
<pre><code>$ docker restart busybox
busybox
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
28d477d3737a busybox &quot;sh&quot; 32 minutes ago Up 3 seconds busybox
</code></pre>
<h4>3进入容器</h4>
<p>处于运行状态的容器可以通过<code>docker attach</code><code>docker exec</code><code>nsenter</code>等多种方式进入容器。</p>
<ul>
<li><strong>使用</strong><code>docker attach</code>命令<strong>进入容器</strong></li>
</ul>
<p>使用 docker attach ,进入我们上一步创建好的容器,如下所示。</p>
<pre><code>$ docker attach busybox
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
7 root 0:00 ps aux
/ #
</code></pre>
<p>注意:当我们同时使用<code>docker attach</code>命令同时在多个终端运行时,所有的终端窗口将同步显示相同内容,当某个命令行窗口的命令阻塞时,其他命令行窗口同样也无法操作。
由于<code>docker attach</code>命令不够灵活,因此我们一般不会使用<code>docker attach</code>进入容器。下面我介绍一个更加灵活的进入容器的方式<code>docker exec</code></p>
<ul>
<li><strong>使用 docker exec 命令进入容器</strong></li>
</ul>
<p>Docker 从 1.3 版本开始,提供了一个更加方便地进入容器的命令<code>docker exec</code>,我们可以通过<code>docker exec -it CONTAINER</code>的方式进入到一个已经运行中的容器,如下所示。</p>
<pre><code>$ docker exec -it busybox sh
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
7 root 0:00 sh
12 root 0:00 ps aux
</code></pre>
<p>我们进入容器后,可以看到容器内有两个<code>sh</code>进程,这是因为以<code>exec</code>的方式进入容器,会单独启动一个 sh 进程,每个窗口都是独立且互不干扰的,也是使用最多的一种方式。</p>
<h4>4删除容器</h4>
<p>我们已经掌握了用 Docker 命令创建、启动和终止容器。那如何删除处于终止状态或者运行中的容器呢?删除容器命令的使用方式如下:<code>docker rm [OPTIONS] CONTAINER [CONTAINER...]</code></p>
<p>如果要删除一个停止状态的容器,可以使用<code>docker rm</code>命令删除。</p>
<pre><code>docker rm busybox
</code></pre>
<p>如果要删除正在运行中的容器,必须添加 -f (或 --force) 参数, Docker 会发送 SIGKILL 信号强制终止正在运行的容器。</p>
<pre><code>docker rm -f busybox
</code></pre>
<h4>5导出导入容器</h4>
<ul>
<li><strong>导出容器</strong></li>
</ul>
<p>我们可以使用<code>docker export CONTAINER</code>命令导出一个容器到文件,不管此时该容器是否处于运行中的状态。导出容器前我们先进入容器,创建一个文件,过程如下。</p>
<p>首先进入容器创建文件</p>
<pre><code>docker exec -it busybox sh
cd /tmp &amp;&amp; touch test
</code></pre>
<p>然后执行导出命令</p>
<pre><code>docker export busybox &gt; busybox.tar
</code></pre>
<p>执行以上命令后会在当前文件夹下生成 busybox.tar 文件,我们可以将该文件拷贝到其他机器上,通过导入命令实现容器的迁移。</p>
<ul>
<li><strong>导入容器</strong></li>
</ul>
<p>通过<code>docker export</code>命令导出的文件,可以使用<code>docker import</code>命令导入,执行完<code>docker import</code>后会变为本地镜像,最后再使用<code>docker run</code>命令启动该镜像,这样我们就实现了容器的迁移。</p>
<p>导入容器的命令格式为 docker import [OPTIONS] file|URL [REPOSITORY[:TAG]]。接下来我们一步步将上一步导出的镜像文件导入到其他机器的 Docker 中并启动它。</p>
<p>首先,使用<code>docker import</code>命令导入上一步导出的容器</p>
<pre><code>docker import busybox.tar busybox:test
</code></pre>
<p>此时busybox.tar 被导入成为新的镜像,镜像名称为 busybox:test 。下面,我们使用<code>docker run</code>命令启动并进入容器,查看上一步创建的临时文件</p>
<pre><code>docker run -it busybox:test sh
/ # ls /tmp/
test
</code></pre>
<p>可以看到我们之前在 /tmp 目录下创建的 test 文件也被迁移过来了。这样我们就通过<code>docker export</code><code>docker import</code>命令配合实现了容器的迁移。</p>
<h3>结语</h3>
<p>到此,我相信你已经了解了容器的基本概念和组成,并已经熟练掌握了容器各个生命周期操作和管理。那容器与镜像的区别,你应该也很清楚了。镜像包含了容器运行所需要的文件系统结构和内容,是静态的只读文件,而容器则是在镜像的只读层上创建了可写层,并且容器中的进程属于运行状态,容器是真正的应用载体。</p>
<p>那你知道为什么容器的文件系统要设计成写时复制(如图 1 所示),而不是每一个容器都单独拷贝一份镜像文件吗?思考后,可以把你的想法写在留言区。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/由浅入深吃透 Docker-完/03 镜像使用Docker 环境下如何配置你的镜像?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/由浅入深吃透 Docker-完/05 仓库访问:怎样搭建属于你的私有仓库?.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997b3ffa373cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>