Docker简介
为什么会有 Docker 出现?
一款产品开发完毕之后想要上线会经历很多步骤,从操作系统到运行环境、再到应用配置等等,都是开发团队和运维团队所需要关心的东西。同时这也是很多互联网公司都不得不面对的问题,特别是各种版本的迭代之后,不同版本环境的兼容,对运维人员都是考验。
环境配置如此麻烦,换一台机器,就要重来一次,费力费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?也就是说,安装的时候,把原始环境一模一样地复制过来。所以Docker便出现了,它给出了一个标准化的解决方案,开发人员利用 Docker 可以消除 "明明在我的机器上运行的好好的" 这样的问题。
之前在服务器配置一个应用的运行环境,要安装各种软件。安装和配置这些东西有多麻烦就不说了,它还不能跨平台。假如我们是在 Windows 上安装的这些环境,到了 Linux 又得重新装。况且就算不跨操作系统,换另一台同样操作系统的服务器,要移植应用也是非常麻烦的。
传统上认为,软件开发 / 测试结束后,所产出的成果即是程序或是能够编译执行的二进制字节码等(java为例)。而为了让这些程序可以顺利执行,开发团队也得准备完整的部署文件,让维运团队得以部署应用程式,开发需要清楚的告诉运维部署团队,用的全部配置文件+所有软件环境。不过,即便如此,仍然常常发生部署失败的状况。Docker镜像的设计,使得Docker得以打破过去「程序即应用」的观念。透过镜像(images)将作业系统核心除外,运作应用程式所需要的系统环境,由下而上打包,达到应用程式跨平台间的无缝接轨运作。
Docker 理念
Docker是基于Go语言实现的云开源项目,Docker的主要目标是"Build,Ship and Run Any App,Anywhere",也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理,使用户的APP(可以是一个WEB应用或数据库应用等等)及其运行环境能够做到 "一次封装,到处运行"。
Linux 容器技术的出现就解决了这样一个问题,而 Docker 就是在它的基础上发展过来的。将应用运行在 Docker 容器上面,而 Docker 容器在任何操作系统上都是一致的,这就实现了跨平台、跨服务器。只需要一次配置好环境,换到别的机子上就可以一键部署好,大大简化了操作。
虚拟机和容器
之前的虚拟机技术
虚拟机(virtual machine)就是带环境安装的一种解决方案,它可以在一种操作系统里面运行另一种操作系统,比如在Windows 系统里面运行 Linux 系统。应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。这类虚拟机完美的运行了另一套系统,能够使应用程序,操作系统和硬件三者之间的逻辑不变。 但是它有如下缺点:
1. 资源占用多
2. 冗余步骤多
3. 启动慢
容器虚拟化技术
由于前面虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)。Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。有了容器,就可以将软件运行所需的所有资源打包到一个隔离的容器中。容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源。系统因此而变得高效轻量并保证部署在任何环境中的软件都能始终如一地运行。
所以 传统虚拟机 和 容器 的区别就很明显了:
传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程
而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便; 每个容器之间互相隔离,每个容器有自己的文件系统 ,容器之间进程不会相互影响,能区分计算资源。
Docker特点
一次构建、到处运行。
更快速的应用交付和部署
传统的应用开发完成后,需要提供一堆安装程序和配置说明文档,安装部署后需根据配置文档进行繁杂的配置才能正常运行。Docker化之后只需要交付少量容器镜像文件,在正式生产环境加载镜像并运行即可,应用安装配置在镜像里已经内置好,大大节省部署配置和测试验证时间。
更便捷的升级和扩缩容
随着微服务架构和Docker的发展,大量的应用会通过微服务方式架构,应用的开发构建将变成搭乐高积木一样,每个Docker容器将变成一块“积木”,应用的升级将变得非常容易。当现有的容器不足以支撑业务处理时,可通过镜像运行新的容器进行快速扩容,使应用系统的扩容从原先的天级变成分钟级甚至秒级。
更简单的系统运维
应用容器化运行后,生产环境运行的应用可与开发、测试环境的应用高度一致,容器会将应用程序相关的环境和状态完全封装起来,不会因为底层基础架构和操作系统的不一致性给应用带来影响,产生新的BUG。当出现程序异常时,也可以通过测试环境的相同容器进行快速定位和修复。
更高效的计算资源利用
Docker是内核级虚拟化,其不像传统的虚拟化技术一样需要额外的 Hypervisor 支持,所以在一台物理机上可以运行很多个容器实例,可大大提升物理服务器的CPU和内存的利用率。
而现在有大量的企业都在应用 Docker,我们熟知的大型公司都在使用。比如:京东、腾讯,内部都在大量的使用 Docker,特别是腾讯。我们知道 Kubernetes(K8S)是一个容器编排管理工具,而腾讯则是对其进行了封装,也就是 TKE(Tencent K8S Engine),专门用来管理内部的容器。
Docker安装
首先 docker 肯定要安装在 Linux 服务器上,这里我以我阿里云上的 CentOS7 为例。安装方式:直接 yum install docker -y 即可。
安装完毕之后,我们来通过 systemctl start docker 命令来启动 docker。
如果后续操作发现报错:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
,那么说明你没有启动 Docker,需要执行 systemctl start docker。
然后我们输入 docker version 即可查看 docker的版本。
然后我们来配置一下镜像加速,因为我们后面要不停地拉取镜像,而默认是从国外的网站进行拉取,所以速度会很慢。我们需要编辑 /etc/docker/daemon.json 文件,在里面输入如下内容:
{
"registry-mirrors": ["https://ww7q1ht1.mirror.aliyuncs.com"]
}
表示从阿里云上面拉取镜像,当然这个链接需要去阿里云上获取,当然上面的链接你也可以使用。
然后别忘记重启 Docker,systemctl restart docker。
docker 安装之后如果不用了,那么要如何卸载呢?
1. systemctl stop docker: 停止docker服务
2. yum remove -y docker: 卸载docker
3. rm -rf /var/lib/docker: 删除docker相关的残留文件
Docker核心概念和底层原理
核心概念
Docker主要有三个核心概念:
镜像(image)
Docker 镜像(Image)就是一个只读的模板。镜像可以用来创建 Docker 容器,一个镜像可以创建很多容器。
容器(container)
Docker 利用容器(Container)独立运行的一个或一组应用,容器是用镜像创建的运行实例。它可以被启动、开始、停止、删除,每个容器都是相互隔离的、保证安全的平台。可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。容器的定义和镜像几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的(后面说)。
仓库(repository)
仓库(Repository)是集中存放镜像文件的场所,每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag)。
仓库分为公开仓库(Public)和私有仓库(Private)两种形式。最大的公开仓库是 Docker Hub(https://hub.docker.com/) ,存放了数量庞大的镜像供用户下载。而国内的公开仓库则包括阿里云 、网易云等。
总结
需要正确的理解 镜像 / 容器 / 仓库 这几个概念,Docker 本身是一个容器运行载体或称之为管理引擎。我们把应用程序和配置依赖打包好形成一个可交付的运行环境,这个打包好的运行环境就叫做 image镜像文件。只有通过这个镜像文件才能生成 Docker 容器。image 文件可以看作是容器的模板,Docker 根据 image 文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。
而 image 文件生成的容器实例,本身也是一个文件。一个容器运行一种服务,当我们需要的时候,就可以通过 Docker 客户端创建一个对应的运行实例,也就是我们的容器。
至于仓储,就是放了一堆镜像的地方,我们可以把镜像发布到仓储中,需要的时候从仓储中拉下来就可以了。
底层原理
Docker 是怎么工作的?
Docker 是一个 Client-Server 结构的系统,Docker守护进程运行在主机上,然后我们通过 Socket 连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器。我们输入命令,Docker 通过 Client 将我们的命令传给 Server,然后守护进程来管理 Docker 所创建的容器,比如删除、重启等等。
所以 Docker 的 logo 很形象,一个鲸鱼飘在大海里,上面背着很多的集装箱。这个大海就是我们的宿主机,直接使用宿主机的资源,大鲸鱼就相当于是 Docker,鲸鱼上面的集装箱就是一个个的容器,里面运行着各种服务,而且每个集装箱都是相互隔离的,不会对其他的集装箱造成污染。
为什么 Docker 比虚拟机快
1)docker有着比虚拟机更少的抽象层。由于 Docker 不需要 Hypervisor 实现硬件资源虚拟化,运行在 Docker 容器上的程序使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上 Docker 将会在效率上有明显优势。
2)Docker 利用的是宿主机的内核,而不需要 Guest OS。因此,当新建一个容器时,Docker 不需要和虚拟机一样重新加载一个操作系统内核。从而避免了引导、加载操作系统内核这个比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载 Guest OS,这个新建过程是分钟级别的。而 Docker 由于直接利用宿主机的操作系统,则省略了这个过程,因此新建一个 Docker 容器只需要几秒钟。
以上是 Docker 的整体架构图,我们看到它和 Redis 是类似的,都是 Client-Server 架构。Docker 内部有一个守护进程,我们通过 Client 发送命令,服务端的守护进程来执行。比如:docker pull 镜像名
是拉取镜像,守护进程在接收到命令之后就会去指定的仓库中拉取指定的镜像,下载到本地;而 docker run 镜像名
则是根据镜像创建一个容器,该容器就可以提供相应的服务。
镜像
下面我们来看看 Docker 的核心之一:镜像。
相关概念
镜像是什么?
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。
UnionFS(联合文件系统)
UnionFS(联合文件系统):UnionFS 是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础,镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
Docker镜像加载原理
Docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。bootfs(boot file system)主要包含 bootloader 和 kernel,bootloader 主要是引导加载 kernel,Linux 刚启动时会加载 bootfs 文件系统,在 Docker镜像的最底层是 bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含 boot 加载器和内核。当 boot 加载完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs 转交给内核,此时系统也会卸载 bootfs。
rootfs(root file system),在 bootfs 之上。包含的就是典型 Linux 系统中的 /dev、/proc、/bin、/etc 等标准目录和文件。rootfs 就是各种不同的操作系统发行版,比如 Ubuntu,CentOS等等。
后面我们会拉取 centos 镜像,你会发现才两百多兆,可平时我们安装进虚拟机的CentOS都是好几个G才对啊?因为对于一个精简的OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用 Host(宿主机)的 kernel,自己只需要提供 rootfs 就行了。由此可见对于不同的linux发行版,bootfs 基本是一致的,rootfs会有差别,因此不同的发行版可以公用 bootfs。
为什么Docker镜像采用分层结构?
我们说 Docker 的镜像实际上是由一层一层的文件系统组成,那么为什么要采用这种分层的结构呢?其实最大的好处就是共享资源,比如:有多个镜像都从相同的 base 镜像构建而来,那么宿主机只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。因为镜像的每一层都可以被共享。
镜像的特点
Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部,这一层通常被称作 "容器层","容器层" 之下的都叫 "镜像层"。
镜像的常用命令
下面我们来看看和镜像相关的命令都有哪些,Docker 和 Redis 一样,都需要我们时刻和命令打交道。先来看几个基础命令。
查看 Docker 版本
docker version
[root@mea ~]# docker version
Client:
Version: 1.13.1
API version: 1.26
Package version: docker-1.13.1-203.git0be3e21.el7.centos.x86_64
Go version: go1.10.3
Git commit: 0be3e21/1.13.1
Built: Thu Nov 12 15:11:46 2020
OS/Arch: linux/amd64
Server:
Version: 1.13.1
API version: 1.26 (minimum version 1.12)
Package version: docker-1.13.1-203.git0be3e21.el7.centos.x86_64
Go version: go1.10.3
Git commit: 0be3e21/1.13.1
Built: Thu Nov 12 15:11:46 2020
OS/Arch: linux/amd64
Experimental: false
我们看到版本是 1.13.1,里面还有一个 Go version,它表示编译 Docker 的 Go 语言版本,因为 Docker 是使用 Go 语言编写的。
查看 Docker 整体信息
docker info
[root@mea ~]# docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 1.13.1
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: journald
Cgroup Driver: systemd
Plugins:
Volume: local
Network: bridge host macvlan null overlay
Swarm: inactive
Runtimes: docker-runc runc
Default Runtime: docker-runc
...........
...........
...........
返回的信息非常多,包括镜像的数量,容器的数量,正在运行、暂停、中止的容器数量,还有操作系统的相关信息等等。
Docker 命令帮助手册
docker --help
以上就是一些基本命令,下面来正式介绍镜像相关的命令。
搜索镜像
docker search [options] 镜像名
里面的 STARTS 就类似于 GitHub 上的 star,越多表示越受欢迎。而且这个命令是有一些可选参数的:
-
--no-trunc: 显示完整的镜像描述, 我们看到图中的 DESCRIPTION 那一列, 后面有的是 ...
-
--filter=stars=n: 列出star数不小于n的镜像
下载镜像
docker pull 镜像名[:TAG], 下载对应版本的镜像
我们看到镜像是分层的,所以下载也是一层一层下载。
注意:拉取镜像的时候可以指定版本,不指定则默认拉取最新的。
查看镜像
docker images [options]
[root@mea ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/mysql 5.6 e61c95a98514 2 days ago 302 MB
docker.io/mysql latest ab2f358b8612 2 days ago 545 MB
docker.io/redis latest ef47f3b6dc11 2 days ago 104 MB
docker.io/nginx latest 7baf28ea91eb 2 days ago 133 MB
docker.io/mongo latest 3068f6bb852e 2 days ago 493 MB
docker.io/centos latest 300e315adb2f 6 days ago 209 MB
解释一下里面的每一列:
REPOSITORY:表示镜像的仓库源
TAG:镜像的标签
IMAGE ID:镜像ID
CREATED:镜像创建时间
SIZE:镜像大小
同一仓库源可以有多个 TAG,代表这个仓库源的不同个版本,我们使用 REPOSITORY:TAG 来定义不同的镜像。如果你不指定一个镜像的版本标签,docker pull mysql:5.6 表示安装5.6版本的 mysql;如果不指定,则默认安装最新的 mysql,也就是 TAG 为 latest。
然后我们看到查看镜像的时候还可以指定可选参数:
-a:列出本地所有的镜像(含中间镜像层)
-q:只显示镜像的id
--digests:显示镜像的摘要信息
--no-trunc:显示完成的镜像信息
删除镜像
docker rmi -f 镜像id[:TAG]
删除镜像的时候如果没有指定 TAG,则表示删除最新的(TAG 为 latest);这里的 -f 表示强制删除,如果没有使用该镜像创建容器的话,那么加不加 -f 是没有区别的。但是一旦该使用该镜像创建了容器并且启动的话,那么要删除必须加上-f,否则报错。值得一提的是,即便删除了镜像,已经创建的容器也不会消失,并且仍然可以正常运行,因为这个容器已经被创建出来了。
[root@mea ~]# docker rmi -f mongo
Untagged: mongo:latest
Untagged: docker.io/mongo@sha256:02e9941ddcb949424fa4eb01f9d235da91a5b7b64feb5887eab77e1ef84a3bad
Deleted: sha256:3068f6bb852ef596c20ca7ccbcd34df94f02d6035a2d83aea1e20e1781ba19b8
Deleted: sha256:ad127f54246fa452c5e783d7895f5de32aa050ca113c26f33cd70d3e321e0b77
Deleted: sha256:df411d930f08ff19c01fff7498a7c161ffe353027d1ee0be99aef4150ef2f237
Deleted: sha256:9da06454ee6eff2960465ba510f4180810bd95d22caa65d958923aeccaa12a12
Deleted: sha256:4fc8092849352849654c290a66285e0b989f01e73a95f869a8ee9bb2eadaebf6
Deleted: sha256:4545ce32b03c37edb33aedeac6ad411047ed6dd94650697e2465db1a86507f93
Deleted: sha256:9abb615f3c3c97191bf206d3228b5bd0978e370394e71984a0d183efb0e0bd9c
Deleted: sha256:eb6c9f1a2a53c149123b96a258dcd25c29b0b8c1709b8a326bc6b9ebaa43dec8
Deleted: sha256:1acc9a7abd496942e1469e91d2f7d2b7d9e0c696f2b392ef1e85d2eaef91e96b
Deleted: sha256:2589aa72cefbcd28ecbf0798bf2ca438af965daedbbb434e8f92d4bb2d689831
Deleted: sha256:9459b6a89846db0723e467610b841e6833dbb2aae6133319a91f2f70c388afac
Deleted: sha256:9a9311f7fcddf94f7476ce89f9703e8360e8cf347ef486a280735f5cf98888cd
Deleted: sha256:b43408d5f11b7b2faf048ae4eb25c296536c571fb2f937b4f1c3883386e93d64
为什么会删除这么多,之前也说过,镜像是分层的,镜像下面还有镜像,但是对外显示的只有一层。至于为什么设计成分层,上面也说了,这是 Docker 镜像的设计原理,但是我们使用就当只有一层就行。
删除多个镜像:docker rmi -f 镜像名1:TAG 镜像名2:TAG······
删除全部镜像:docker rmi -f $(docker images -qa) 或者 docker images -qa | xargs docker rmi -f
容器
下面来看看容器,我们说镜像是用来创建容器的模板;而镜像显然是无法提供服务的,真正提供服务的是根据镜像创建的容器。
Docker 镜像都是只读的,而当基于镜像创建并启动一个容器时,一个新的可写层被加载到镜像的顶部。这一层通常被称作 "容器层","容器层" 之下的都叫 "镜像层"。
新建并启动容器
docker run [options] 镜像名
创建容器时,可选参数是非常重要的,我们来看一下。
--name="容器新名字": 为容器指定一个名称;
-d: 后台运行容器,并返回容器ID,也即启动守护式容器;
-i:以交互模式运行容器,通常与 -t 同时使用;
-t:为容器重新分配一个伪输入终端,通常与 -i 同时使用;
-P: 随机端口映射;
-p: 指定端口映射;
下面来通过交互式启动容器。
[root@mea ~]# docker run -it centos
[root@e8eafccef263 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@e8eafccef263 /]# echo "hello world"
hello world
[root@e8eafccef263 /]#
此时我们便进入了使用 Docker 启动的 centos,这个centos 只保留了最核心的部分,使用的资源都是宿主机的资源。
列出当前所有正在运行的容器
docker ps [options]
依旧先来看一下可选参数,如果不指定可选参数,则列出当前正在运行的容器。
-a: 列出当前所有正在运行的容器+历史上运行过的
-l: 显示最近创建的容器
-n count: 显示最近count个创建的容器
-q: 静默模式, 只显示容器编号
--no-trunc: 不截断输出
看一下里面的每一列:
container id:容器的id
image:容器是由哪个镜像创建的
created:创建时间
status:容器状态,是在运行啊,还是在多长时间之前退出
names:容器的名字
退出容器
退出容器有两种方式:
exit:容器停止、退出
[root@mea ~]# docker run -it centos
[root@b3c3876b264f /]# exit
exit
[root@mea ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@mea ~]#
exit 之后,使用 docker ps 查看,发现没有正在运行的容器。
ctrl+p+q:容器不停止、退出
[root@mea ~]# docker run -it centos
# 按下 ctrl+p+q
[root@39188248a72d /]#
[root@mea ~]#
[root@mea ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
39188248a72d centos "/bin/bash" 15 seconds ago Up 14 seconds blissful_jang
[root@mea ~]#
按下 ctrl+p+q 之后,使用 docker ps 发现之前的容器还在运行。
启动容器
docker start 容器id
启动之前创建的容器。
一开始是没有正在运行的容器的,但是这个容器确实被创建出来了,只不过退出了。我们通过最近创建的容器找到 id 或者 name,然后 docker start 容器id 进行启动。顺便一提这个id可能有点长,我们也可以只输入前几位,比如 6 位。
重启容器
docker restart 容器id
重新启动一个正在运行的容器,另外一般重新启动都是针对正在运行的容器来说,就像Windows,重新启动只有电脑开机之后,才有重新启动这一说。但是 docker restart 也可以对没有启动的容器使用,等于 start;同时 start 也可以对已经启动的容器来使用。
停止容器
docker stop 容器id
停止正在运行的容器,即便容器没有运行,也可以使用这个命令,会返回容器的id。
[root@mea ~]# docker run -it centos
[root@1e1045eec69a /]# exit
exit
[root@mea ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@mea ~]#
[root@mea ~]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e1045eec69a centos "/bin/bash" 9 seconds ago Exited (0) 6 seconds ago nifty_liskov
[root@mea ~]#
[root@mea ~]# docker stop 1e1045eec69a
1e1045eec69a
[root@mea ~]#
强制停止容器
docker kill 容器id
和 docker stop 功能一样,但是更加粗暴;stop 类似于关机,kill 类似于拔电源。
删除容器
删除已停止的容器:docker rm 容器id,所以删除镜像是 rmi、删除容器是 rm。
[root@mea ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@mea ~]# docker ps -n 2
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e1045eec69a centos "/bin/bash" 5 minutes ago Exited (0) 5 minutes ago nifty_liskov
[root@mea ~]# docker rm 1e10
1e10
[root@mea ~]#
会返回删除的容器的id,当然也可以同时删除多个;但如果容器正在运行的话,则是删不掉的,如果想删掉一个正在运行的容器,则需要通过 docker rm -f 容器id
的方式来删除。
删除所有容器:docker rm -f $(docker ps -qa)
或者 docker ps -qa | xargs docker rm -f
直接删除是删不掉的,提示我们无法删除一个正在运行的容器,如果想删除应该先将容器停止掉,或者使用 -f 参数。
启动守护式容器
我们说启动容器的时候,指定 -i 参数则是以交互式方式启动;再指定 -t 的话,会分配一个伪终端,这两个参数一般搭配使用。如果我们指定 -i 但是不指定 -t 的话,看看会有什么结果:
[root@mea ~]#
[root@mea ~]# docker run -i centos
ls /root
anaconda-ks.cfg
anaconda-post.log
original-ks.cfg
yoyoyo
/bin/bash: line 4: yoyoyo: command not found
ext
/bin/bash: line 7: ext: command not found
exit
[root@mea ~]#
我们看到虽然也是交互式的,但是很明显终端没了。
而除了 -i 和 -t 之外,我们还可以指定为 -d,表示启动守护式容器。我们说一个容器就类似于一个精简的虚拟机,每个容器提供一种服务,而服务一般显然都是后台启动的。
[root@mea ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@mea ~]#
[root@mea ~]# docker run -d centos
5c8c89a6f62a99729767cb2791194006656437a2950c60322a968fbbfc3b5f45
[root@mea ~]#
[root@mea ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@mea ~]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5c8c89a6f62a centos "/bin/bash" 56 seconds ago Exited (0) 54 seconds ago suspicious_shockley
[root@mea ~]#
我们发现在启动之后,使用docker ps并没有列出,而使用docker ps -l查看,发现已经退出了,这是怎么回事?
很重要的一点:Docker容器后台运行,就必须有一个前台进程;而容器运行的命令如果不是那些一直挂起的命令(比如运行top,tail),就是会自动退出的。
这个是 Docker 的机制问题,比如你的 web 容器,我们以nginx为例,正常情况下,我们配置启动服务只需要启动响应的 service 即可。例如:service nginx start,但是这样做,nginx为后台进程模式运行,就导致docker前台没有运行的应用,这样的容器后台启动后,会立即自杀,因为他觉得他没事可做了。所以,最佳的解决方案是,将你要运行的程序以前台进程的形式运行。所以像 nginx、redis等镜像在启动之后,内部的进程都是以前台方式启动的。
所以 docker run -d 镜像 或者 docker run 镜像之后,会直接返回一串id,因为容器启动之后会立即自杀,尽管如此,但是这个容器已经被创建出来了。但是当我们使用 -it 的时候不会自动退出,因为相当于创建了一个伪终端
查看容器日志
docker logs 容器id
也可以指定一些可选参数:
-t: 加入当前时间
-f: 类似于 tail -f, 跟随最新的日志打印
--tail 数字: 显示最后多少条
上面的语法我们后面会说,但是我们是使用 -d 启动的,不是说启动之后就会被杀死吗?如果启动之后退出,说明没有相应的前台进程,但是这里不一样,这里我们是写了一个死循环的。每隔两秒钟打印一次 "hello world"。
查看容器内部运行的进程
docker top 容器id
[root@mea ~]# docker top 654e228f4436
UID PID PPID C STIME TTY TIME CMD
root 15589 15572 0 17:46 ? 00:00:00 /bin/sh -c while true;do echo hello world;sleep 2;done
root 15836 15589 0 17:52 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 2
[root@mea ~]#
查看容器内部细节
docker inspect 容器id
返回的内容非常多,详细地描述了该容器。
进入正在运行的容器并与之交互
之前其实有一个问题没有说,是什么呢?那就是当我们使用ctrl+p+q的时候,会在不停止容器的情况下退出,容器会依旧在运行,但是如果我们想要再次进入已经停止的容器的话,该怎么办呢?
docker attach 容器id
[root@mea ~]# docker run -it centos
[root@08eb7761dca5 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@08eb7761dca5 /]#
[root@mea ~]#
[root@mea ~]# docker attach 08eb7761dca5
[root@08eb7761dca5 /]#
这个命令会直接启动命令行的终端,不会启动新的进程。
docker exec -it 容器id /bin/sh
[root@mea ~]# docker exec -it 08eb7761dca5 /bin/sh
sh-4.4# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
sh-4.4# pwd
/
sh-4.4#
这个命令是在容器中打开新的终端,比如说,我们在 Linux 上开了一个终端,attach 是在原来的终端上执行,exec则是新打开了一个终端,并且可以启动一个新的进程。
并且我们可以直接执行 shell 命令:
[root@mea ~]# docker exec -it 08eb7761dca5 /bin/sh -c "ls /"
bin etc lib lost+found mnt proc run srv tmp var
dev home lib64 media opt root sbin sys usr
[root@mea ~]#
也就是开启一个新的终端,然后执行,只不过执行完之后自动回到宿主机。
-c ""
可以同时写多个命令,中间使用分号隔开。
从容器内拷贝文件到主机上
比如我这个容器不想要了,但是我里面有一个很重要的文件,这个文件我想把它拿到宿主机上,该怎么做呢?
docker cp 容器id:容器路径 目的主机路径
[root@mea ~]# ls | grep 1.txt
[root@mea ~]#
[root@mea ~]# docker exec -it 08eb7761dca5 /bin/sh -c "cd /root;touch 1.txt"
[root@mea ~]#
[root@mea ~]# docker cp 08eb7761dca5:/root/1.txt /root
[root@mea ~]#
[root@mea ~]# ls | grep 1.txt
1.txt
[root@mea ~]#
镜像打包
说到拷贝文件,我想起了镜像。在镜像的时候忘记说了。如果没有网络,我们如何将镜像打包拷贝到另一个机器上呢,既然要拷贝到另一台机器上,肯定要先拷贝到本机上。
docker save 镜像名 > xxx.tar
[root@mea ~]# ll
total 0
[root@mea ~]# docker save nginx > nginx.tar
[root@mea ~]# ll
total 133880
-rw-r--r-- 1 root root 137091072 Dec 15 11:24 nginx.tar
[root@mea ~]#
docker load < xxx.tar
将tar文件加载成镜像,在保存镜像是一般以 tar 结尾,可能有人发现在加载镜像的时候没有指定镜像名,这是因为 tar 文件中包含了镜像的所有信息。
[root@mea ~]# docker rmi -f nginx
Untagged: nginx:latest
Untagged: docker.io/nginx@sha256:31de7d2fd0e751685e57339d2b4a4aa175aea922e592d36a7078d72db0a45639
Deleted: sha256:7baf28ea91eb59a68b3b3a82873d413dc9f1b6e0b89d5ad627ad80154f546be5
Deleted: sha256:76568a8765bf6788bab06a4c283b5ca6669def93eba5c1f0fc887b159f6e1dc1
Deleted: sha256:ef3c14608491697162ea3cc095a1c0c27cd6d4053aa3ea09f91c756379ebc8d1
Deleted: sha256:b538d73544a75eec8322716997990d289f2581169a82b763a0e1037c669f167d
Deleted: sha256:89942a8c1027ff865a14b0a807142453493474831ebc47c312f05ec640b16254
[root@mea ~]# docker images | grep nginx
[root@mea ~]#
[root@mea ~]# docker load < nginx.tar
2111bafa5ce4: Loading layer [==================================================>] 64.55 MB/64.55 MB
f3ee98cb305c: Loading layer [==================================================>] 3.072 kB/3.072 kB
997bdb5b26cc: Loading layer [==================================================>] 4.096 kB/4.096 kB
ea6033164031: Loading layer [==================================================>] 3.584 kB/3.584 kB
Loaded image: docker.io/nginx:latest
[root@mea ~]#
[root@mea ~]# docker images | grep nginx
docker.io/nginx latest 7baf28ea91eb 3 days ago 133 MB
[root@mea ~]#
将镜像 nginx 删除,然后再 load,发现镜像 nginx 又回来了。
查看容器内部的变化
docker diff 容器id
查看一个镜像的形成历史
docker history 镜像名
暂停一个容器
docker pause 容器id
恢复暂停的容器
docker unpause 容器id
阻塞、直到容器退出、然后打印退出时候的状态值
docker wait 容器id
镜像commit:将一个容器变成一个镜像
docker commit -m="提交的容器信息" -a="作者" 容器id 要创建的镜像名[:TAG]
比如我们启动了一个容器,在这个容器里面我们做了相应的操作,我们想把当前这个已经做了操作的容器变成一个镜像。
**我们运行一下 nginx 镜像: **
[root@mea ~]# docker run -d -p 90:80 nginx
8d3d36eeaf5c4c28164f7ed61f3fb02f859461db3590651ba3eaf5394ada8041
[root@mea ~]#
[root@mea ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8d3d36eeaf5c nginx "/docker-entrypoin..." 4 seconds ago Up 3 seconds 0.0.0.0:90->80/tcp gallant_yalow
[root@mea ~]#
注意一下这里的 -p 90:80,我们说一个容器就类似于一个小型的 CentOS,比如这里的nginx容器,它内部监听80端口,这个80端口指的是容器(小型CentOS)的80端口。而 -p 90:80 里面的 90 指的是和容器内部 80 端口绑定的宿主机的端口,因为外界不能直接访问容器,需要通过宿主机的 90 端口映射到容器的 80 端口,访问服务。
所以我们可以启动N个nginx,每个nginx都监听80端口,但是这个80端口是每个容器内部的80端口,它们是彼此隔离的。而和宿主机绑定的端口则不能重复,比如第一个nginx和宿主机的90端口绑定,那么第二个容器就不能再和90端口绑定了。
而我们从外界访问的话,只能通过90端口访问,因为要先访问到宿主机才能访问到容器。
[root@mea ~]# docker exec -it 8d3d36 /bin/sh
# cd /usr/share/nginx/html
# find -name \'index.html\' | xargs perl -pi -e \'s|nginx|夏色祭|g\'
# exit
[root@mea ~]# docker commit -m="index.html被修改的nginx" -a="哟哟哟" 8d3d36 my_nginx:3.3
sha256:535ad9516d9cb4d0408de8520158cfcb38baa578cacad01fa88b73d05ac5c905
[root@mea ~]# docker images | grep my_nginx
my_nginx 3.3 535ad9516d9c 10 seconds ago 133 MB
[root@mea ~]#
我们进入容器,将里面的 index.html 给改掉,然后将其打包成镜像。然后启动我们新打包的镜像:
[root@mea ~]# docker run -d -p 100:80 my_nginx:3.3
ac1415d33481d03a7f906402a0c079322cc0b1aa957b546195f8b135060d7706
[root@mea ~]#
[root@mea ~]# docker ps | grep my_nginx
ac1415d33481 my_nginx:3.3 "/docker-entrypoin..." 13 seconds ago Up 13 seconds 0.0.0.0:100->80/tcp eager_keller
[root@mea ~]#
90端口被之前的nginx容器给占了,所以我们需要绑定其它的宿主机端口。另外关于启动镜像,如果镜像有 TAG(或者 TAG 不是 latest),那么启动的时候需要指定 TAG,因为默认启动的是 TAG 为 latest 的镜像。如果发现不存在此镜像,会自动从仓库中拉取。
我们将 "nginx" 换成了 "夏色祭",但是比较尴尬的是忘记编码了,不过这不重要。重要的是我们启动容器之后,没有做任何的修改,但是显示的内容变了,因为此镜像是由配置改变的容器commit得到的。
因此我们可以看到,除了可以用镜像生成容器之外,还可以将容器commit成一个镜像。
常用命令总结
部分命令会在后面介绍。
attach Attach to a running container # 当前 shell 下 attach 连接指定运行镜像
build Build an image from a Dockerfile # 通过 Dockerfile 定制镜像
commit Create a new image from a container changes # 提交当前容器为新的镜像
cp Copy files/folders from the containers filesystem to the host path #从容器中拷贝指定文件或者目录到宿主机中
create Create a new container # 创建一个新的容器,同 run,但不启动容器
diff Inspect changes on a container\'s filesystem # 查看 docker 容器变化
events Get real time events from the server # 从 docker 服务获取容器实时事件
exec Run a command in an existing container # 在已存在的容器上运行命令
export Stream the contents of a container as a tar archive # 导出容器的内容流作为一个 tar 归档文件[对应 import ]
history Show the history of an image # 展示一个镜像形成历史
images List images # 列出系统当前镜像
import Create a new filesystem image from the contents of a tarball # 从tar包中的内容创建一个新的文件系统映像[对应export]
info Display system-wide information # 显示系统相关信息
inspect Return low-level information on a container # 查看容器详细信息
kill Kill a running container # kill 指定 docker 容器
load Load an image from a tar archive # 从一个 tar 包中加载一个镜像[对应 save]
login Register or Login to the docker registry server # 注册或者登陆一个 docker 源服务器
logout Log out from a Docker registry server # 从当前 Docker registry 退出
logs Fetch the logs of a container # 输出当前容器日志信息
port Lookup the public-facing port which is NAT-ed to PRIVATE_PORT # 查看映射端口对应的容器内部源端口
pause Pause all processes within a container # 暂停容器
ps List containers # 列出容器列表
pull Pull an image or a repository from the docker registry server # 从docker镜像源服务器拉取指定镜像或者库镜像
push Push an image or a repository to the docker registry server # 推送指定镜像或者库镜像至docker源服务器
restart Restart a running container # 重启运行的容器
rm Remove one or more containers # 移除一个或者多个容器
rmi Remove one or more images # 移除一个或多个镜像[无容器使用该镜像才可删除,否则需删除相关容器才可继续或 -f 强制删除]
run Run a command in a new container # 创建一个新的容器并运行一个命令
save Save an image to a tar archive # 保存一个镜像为一个 tar 包[对应 load]
search Search for an image on the Docker Hub # 在 docker hub 中搜索镜像
start Start a stopped containers # 启动容器
stop Stop a running containers # 停止容器
tag Tag an image into a repository # 给源中镜像打标签
top Lookup the running processes of a container # 查看容器中运行的进程信息
unpause Unpause a paused container # 取消暂停容器
version Show the docker version information # 查看 docker 版本号
wait Block until a container stops, then print its exit code # 截取容器停止时的退出状态值
Docker容器数据卷
先来看看Docker的理念:
将运用与运行的环境打包形成容器运行,运行可以伴随着容器,但是我们对数据的要求希望是持久化的
容器之间希望有可能共享数据
Docker容器产生的数据,如果不通过docker commit生成新的镜像,使得数据做为镜像的一部分保存下来,那么当容器删除后,数据自然也就没有了。我们之前介绍了一个docker cp命令,可以将容器内的数据拷贝到宿主机当中,但是有没有更简单的办法呢?可以不用我们显式的调用命令,而是实现自动关联,我们在容器中新建文件或者修改文件可以自动地同步到宿主机当中呢?答案是可以的,在docker中我们使用卷的方式。
卷就是目录或文件,存在于一个或多个容器中,由Docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性。卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷:
1. 数据卷可在容器之间共享或重用数据
2. 卷中的更改可以直接生效
3. 数据卷中的更改不会包含在镜像的更新中
4. 数据卷的生命周期一直持续到没有容器使用它为止
核心就是:容器的持久化,以及容器间的继承+共享数据。
直接命令添加
docker run -it -v /宿主机绝对路径:/容器绝对路径 镜像名
[root@mea ~]# ll
total 0
[root@mea ~]# docker run -it -v /root/host:/container centos
[root@df66650cce72 /]#
[root@df66650cce72 /]# ls | grep container
container
[root@df66650cce72 /]# exit
exit
[root@mea ~]# ll
total 4
drwxr-xr-x 2 root root 4096 Dec 15 14:03 host
[root@mea ~]#
一开始宿主机内没有任何内容,然后我们启动容器,将宿主机的 /root/host 和 容器的 /container 进行关联,显然这两个目录各自都不存在。但是当启动之后,它们就被自动创建了。
然后此时宿主机的 /root/host 和 容器的 /container 就实现了共享,在其中一个目录中做的任何修改都会影响到另一目录。
如果你在启动容器的时候发现失败了,提示没有权限,那么需要加上一个可选参数。
docker run -it --privileged=true -v /宿主机绝对路径:/容器绝对路径 镜像名
然后我们测试一下数据是否真的会共享:
[root@mea ~]# touch host/1.txt
[root@mea ~]#
[root@mea ~]# docker start df66650cce72
df66650cce72
[root@mea ~]# docker exec -it df66650cce72 /bin/sh
sh-4.4# ls /container/
1.txt
sh-4.4# touch /container/2.txt
sh-4.4# exit
exit
[root@mea ~]# ll host/
total 0
-rw-r--r-- 1 root root 0 Dec 15 14:10 1.txt
-rw-r--r-- 1 root root 0 Dec 15 14:11 2.txt
[root@mea ~]#
我们在宿主机的 host 的目录中创建 1.txt 文件,然后启动容器(注意:容器刚才是关闭的),查看 /container 目录,发现内部的 1.txt 被自动创建了。然后在容器的 /container 内部创建 2.txt,发现也被同步到宿主机中了。
同理,我们对里面的文件本身做修改,同样会实现共享。
[root@mea ~]# docker exec -it df66650cce72 /bin/sh
sh-4.4# cat /container/1.txt
sh-4.4# cat /container/2.txt
sh-4.4#
sh-4.4# echo "matsuri" > /container/1.txt
sh-4.4# exit
exit
[root@mea ~]#
[root@mea ~]# cat host/1.txt
matsuri
[root@mea ~]# echo "matsuri" > /root/host/2.txt
[root@mea ~]#
[root@mea ~]# docker exec -it df66650cce72 /bin/sh
sh-4.4# cat /container/2.txt
matsuri
sh-4.4#
我们进入容器,看到里面的文件都是没有内容的,然后向 1.txt 中写入内容,回到宿主机中发现 host 目录下的 1.txt 已经有内容了。然后在 host 目录下的 2.txt 中也写入内容,再进入容器,看到 /container 目录下的 2.txt 中也有内容了。
所以在目录中做任何的修改,都会同步到另一个目录中。
并且我们在操作的时候,是使用exit直接退出容器,并不是使用ctrl+p+q。也就是说,我们在宿主机操作的时候,容器时处于关闭状态的。这种情况下依旧会同步,类似于持久化,当容器启动之后再将数据同步过去就可以了。
当然使用 ctrl+p+q 就更不用说了。
如果我们在同步之后,禁止容器内部修改文件,只能在宿主机中修改,该怎么做呢?
docker run -it --privileged=true -v /宿主机绝对路径:/容器绝对路径:ro 镜像名
只需要在容器的目录后面加上一个 ro 即可,表示read only,只读。
[root@mea ~]# docker run -it -v /root/host:/container:ro centos
[root@900695bdf841 /]# ls /container/
1.txt 2.txt
[root@900695bdf841 /]# touch /container/3.txt
touch: cannot touch \'/container/3.txt\': Read-only file system
[root@900695bdf841 /]#
[root@900695bdf841 /]# echo "hello matsuri" >> /container/1.txt
bash: /container/1.txt: Read-only file system
[root@900695bdf841 /]#
这个容器是我们新创建的,但是我们看到里面居然有文件。因为宿主机内部有文件,虽然当时我们把容器删掉了,但如果是删除整个容器的话,宿主机内部的目录和目录里面的文件是不受影响的。当然,如果关联容器目录本身就有文件,那么容器内的文件在启动的时候也会同步到宿主机上。
然后我们在容器内的 /container 目录创建文件、修改文件都是不允许的,因为它是只读的,当然在其它目录创建是可以的。
因此可以看到,如果是以只读方式创建容器,那么在宿主机里面是可以修改并创建文件的,但是在容器里面不行,至于数据本身,在宿主机里面进行的操作依旧会进行同步。
我们使用 docker inspect 查看一下容器的内部细节:
DockerFile添加
DockerFile会在下面详细介绍,但是现在可以提前了解一下。DockerFile相当于是对镜像的描述,可以对DockerFile进行build,得到镜像。如果我们想修改或者创建镜像的话,那么就可以修改或者创建DockerFile文件。DockerFile相当于是源码文件,镜像相当于是编译之后的字节码。Python运行的也是字节码文件,如果我们想修改字节码,那么就要修改源码,再重新编译为字节码。DockerFile也是一样的。
新建一个文件,就叫dockerfile,写入如下内容:
FROM centos
VOLUME ["/root/dataVolumeContainer1","/root/dataVolumeContainer2"]
CMD echo "finished,--------success1"
CMD /bin/bash
dockerfile会在下一章介绍,先来简单地看一看。首先是第一行的 FROM centos,相当于继承,extend。之前说过镜像是分层的,这样可以共享。比如tomcat,总共四百多兆,这显然太大了。但是如果看tomcat的DockerFile文件的话,会发现开头写着from open-jdk1.8,说明tomcat是包含了jdk的,所以才会这么大。不然只有tomcat没有jdk是没法运行的,因此在删除tomcat的时候,会发现删除的不止一层。镜像就像花卷或者俄罗斯套娃一样,一层套着一层。
VOLUME则是数据卷,里面可以有多个目录,这些目录会自动和宿主机内的目录进行关联。就像 -v 一样,当然我们使用命令添加数据卷的时候也可以关于多个目录,比如:
-v /host1:/container1 -v /host2/container2
但是我们说 VOLUME 里面目录会自动和宿主机里面的目录进行关联,那宿主机目录在哪里指定呢?答案是不需要指定,因为出于可移植和分享的考虑,用 -v 主机目录:容器目录
这种方法不能够直接在Dockerfile中实现。由于宿主机目录是依赖于特定宿主机的,并不能够保证在所有的宿主机上都存在这样的特定目录。所以我们只需要指定容器目录即可,宿主机目录 docker 会自动创建。
而最后两个 CMD 则不用管,后面说。
然后生成镜像
以上是关于python实现websocket的主要内容,如果未能解决你的问题,请参考以下文章 从python代码连接到Flask websocket [重复]docker build -f Do