后台服务器:https://course.0voice.com/v1/course/intro?courseId=5&agentId=0
docker是一个用Go语言实现的开源项目,可以让我们方便的创建和使用容器,docker将程序以及程序所有的依赖都打包到docker container,这样你的程序可以在任何环境都会有一致的表现,这里程序运行的依赖也就是容器就好比集装箱,容器所处的操作系统环境就好比货船或港口,程序的表现只和集装箱有关系(容器),和集装箱放在哪个货船或者哪个港口(操作系统)没有关系。
因此我们可以看到docker可以屏蔽环境差异,也就是说,只要你的程序打包到了docker中,那么无论运行在什么环境下程序的行为都是一致的,程序员再也无法施展表演才华了,不会再有“在我的环境上可以运行”,真正实现“build once, run everywhere”。
此外docker的另一个好处就是快速部署,这是当前互联网公司最常见的一个应用场景,一个原因在于容器启动速度非常快,另一个原因在于只要确保一个容器中的程序正确运行,那么你就能确信无论在生产环境部署多少都能正确运行。
docker出现之前,部署软件到不同环境所需的工作量巨大:
Docker的好处
容器和虚拟机的区别
vm(虚拟机)与docker(容器)框架,直观上来讲vm多了一层guest OS,同时Hypervisor会对硬件资源进行虚拟化,docker直接使用硬件资源,所以资源利用率相对docker低也是比较容易理解的。服务器虚拟化解决的核心问题是资源调配,而容器解决的核心问题是应用开发、测试和部署。虚拟机技术通过Hypervisor层抽象底层基础设施资源,提供相互隔离的虚拟机,通过统一配置、统一管理,计算资源的可运维性,以及资源利用率都能够得到有效的提升。同时,虚拟机提供客户机操作系统,客户机变化不会影响宿主机,能够提供可控的测试环境,更能够屏蔽底层硬件甚至基础软件的差异
性,让应用做到的广泛兼容。然而,再牛逼的虚拟化技术,都不可避免地出现计算、IO、网络性能损失,毕竟多了一层软件,毕竟要运行一个完整的客户机操作系统。容器技术严格来说并不是虚拟化,没有客户机操作系统,是共享内核的。容器可以视为软件供应链的集装箱,能够把应用需要的运行环境、缓存环境、数据库环境等等封装起来,以最简洁的方式支持应用运行,轻装上阵,当然是性能更佳。Docker镜像特性则让这种方式简单易行。当然,因为共享内核,容器隔离性也没有虚拟机那么好 。
Docker基本概念
Docker 包括三个基本概念:
Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。
Docker 容器通过 Docker 镜像来创建。容器与镜像的关系类似于面向对象编程中的对象与类。
Docker | 面向对象 |
---|---|
容器 | 对象 |
镜像 | 类 |
准备工作
Docker CE 支持以下版本的 Ubuntu 操作系统:
Ubuntu Focal 20.04 (LTS)
Ubuntu Bionic 18.04 (LTS)
Ubuntu Xenial 16.04 (LTS)
卸载旧版本
旧版本的 Docker 称为 docker 或者 docker-engine ,使用以下命令卸载旧版本:
sudo apt-get remove docker docker-engine docker.io containerd runc
使用 apt安装
由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改。因此,我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。
$ sudo apt-get update
$ sudo apt-get install
ca-certificates
curl
gnupg
lsb-release
鉴于国内网络问题,强烈建议使用国内源,官方源请在注释中查看。为了确认所下载软件包的合法性,需要添加软件源的 GPG 密钥。
# 国内源,使用这里的源
curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# 官方源,用了国内源,这里就不用再执行
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
安装 Docker CE
更新 apt 软件包缓存,并安装 docker-ce :
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
如果需要安装指定版本,请参考:https://docs.docker.com/engine/install/ubuntu/ To install a specific version of Docker Engine
查看一下docker版本
zhen@zhen:/home/wz/mynginx/composetest$ docker -v
Docker version 20.10.16, build aa7e414
卸载docker
如果需要卸载,不需要就别卸载。
卸载 Docker Engine, CLI,和Containerd 包
sudo apt-get purge docker-ce docker-ce-cli containerd.io
删除残留文件:
sudo rm -rf /var/lib/docker
启动 Docker CE
开机启动: sudo systemctl enable docker
启动: docker:sudo systemctl start docker
建立 docker 用户组
默认情况下, docker 命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root用户。因此,更好地做法是将需要使用 docker 的用户加入 docker 用户组。建立 docker 组 :
sudo groupadd docker
将当前用户加入 docker 组:
sudo usermod -aG docker $USER
退出当前终端并重新登录,进行如下测试。
zhen@zhen:/home/wz/mynginx/composetest$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:
- The Docker client contacted the Docker daemon.
- The Docker daemon pulled the “hello-world” image from the Docker Hub.
(amd64)- The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.- The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/For more examples and ideas, visit:
https://docs.docker.com/get-started/
若能正常输出以上信息,则说明安装成功。
Docker 运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker 会从镜像仓库下载该镜
像。
本章将介绍更多关于镜像的内容,包括:
从仓库获取镜像;
管理本地主机上的镜像;
介绍镜像实现的基本原理。
Explore Docker’s Container Image Repository | Docker Hub
上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像。从 Docker 镜像仓库获取镜像的命令是 docker pull 。其命令格式为:
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
具体的选项可以通过 docker pull --help 命令看到,这里我们说一下镜像名称的格式。
zhen@zhen:/home/wz$ docker pull redis:6.0.8
6.0.8: Pulling from library/redis
bb79b6b2107f: Pull complete
1ed3521a5dcb: Pull complete
5999b99cee8f: Pull complete
3f806f5245c9: Pull complete
f8a4497572b2: Pull complete
eafe3b6b8d06: Pull complete
Digest: sha256:21db12e5ab3cc343e9376d655e8eabbdbe5516801373e95a8a9e66010c5b8819
Status: Downloaded newer image for redis:6.0.8
docker.io/library/redis:6.0.8
上面的命令中没有给出 Docker 镜像仓库地址,因此将会从 Docker Hub 获取镜像。而镜像名称是redis:6.0.8,因此将会获取官方镜像 library/ redis仓库中标签为 6.0.8 的镜像。从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的 sha256 的摘要,以确保下载一致性。在使用上面命令的时候,你可能会发现,你所看到的层 ID 以及 sha256 的摘要和这里的不一样。这是因为官方镜像是一直在维护的,有任何新的 bug,或者版本更新,都会进行修复再以原来的标签发布,这样可以确保任何使用这个标签的用户可以获得更安全、更稳定的镜像。
有了镜像后,我们就能够以这个镜像为基础启动并运行一个容器。以上面的 redis:6.08 为例,如果我们打算启动里面的 bash 并且进行交互式操作的话,可以执行下面的命令。
zhen@zhen:/home/wz$ docker run -it --rm redis:6.0.8 bash
root@9cc6017c54e2:/data#
docker run 就是运行容器的命令,我们这里简要的说明一下上面用到的参数。
进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里,我们执行了 ‘redis-server’ ,这是 Linux 常用的查看当前系统版本的命令,从返回的结果可以看到容器内是 安装了redis6.0.8 系统
root@81faed055ffe:/data# redis-server
8:C 05 Jun 2022 01:42:53.703 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
8:C 05 Jun 2022 01:42:53.703 # Redis version=6.0.8, bits=64, commit=00000000, modified=0, pid=8, just started
8:C 05 Jun 2022 01:42:53.703 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
.
.-__ ''-._ _.-
.
. ‘’-._ Redis 6.0.8 (00000000/0) 64 bit
.-.-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.
-.|'_.-'| Port: 6379 |
-.._ / _.-' | PID: 8
-._-._
-./ .-’ .-’
|-._
-.-.__.-' _.-'_.-'| |
-.-._ _.-'_.-' | http://redis.io
-._-._
-..-'.-’ .-’
|-._
-.-.__.-' _.-'_.-'| |
-.-._ _.-'_.-' |
-._-._
-..-‘_.-’ _.-’
-._
-..-’ _.-’
-._ _.-'
-..-’8:M 05 Jun 2022 01:42:53.704 # Server initialized
8:M 05 Jun 2022 01:42:53.704 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add ‘vm.overcommit_memory = 1’ to /etc/sysctl.conf and then reboot or run the command ‘sysctl vm.overcommit_memory=1’ for this to take effect.
8:M 05 Jun 2022 01:42:53.704 * Ready to accept connections
最后我们通过 exit
退出了这个容器。
使用 docker image ls查看本地已有的镜像。
zhen@zhen:/home/wz$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
composetest_web latest 31d233a3adc0 6 days ago 60.6MB
127.0.0.1:5000/mynginx latest 7489ae3bb3a0 6 days ago 142MB
mynginx latest 7489ae3bb3a0 6 days ago 142MB
nginx v3 7489ae3bb3a0 6 days ago 142MB
nginx latest 0e901e68141f 7 days ago 142MB
registry latest 773dbf02e42e 9 days ago 24.1MB
redis alpine c3ea2db12504 10 days ago 28.4MB
python 3.7-alpine 7642396105af 10 days ago 45.5MB
hello-world latest feb5d9fea6a5 8 months ago 13.3kB
redis 6.0.8 16ecd2772934 19 months ago 104MB
果要删除本地的镜像,可以使用 docker image rm 命令,其格式为:
docker image rm [选项] <镜像1> [<镜像2> …]
其中, <镜像> 可以是 镜像短 ID 、 镜像长 ID 、 镜像名 或者 镜像摘要 。
我们可以用镜像的完整 ID,也称为 长 ID ,来删除镜像。使用脚本的时候可能会用长 ID,但是人工输入就太累了,所以更多的时候是用 短 ID 来删除镜像。 docker image ls 默认列出的就已经是短 ID了,一般取前3个字符以上,只要足够区分于别的镜像就可以了。
如果观察上面这几个命令的运行输出信息的话,你会注意到删除行为分为两类,一类是 Untagged ,另一类是 Deleted 。镜像的唯一标识是其 ID 和摘要,而一个镜像可以有多个标签。因此当我们使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged 的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete 行为就不会发生。所以并非所有的 docker rmi 都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。
当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变动非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。这就是为什么,有时候会奇怪,为什么明明没有别的标签指向这个镜像,但是它还是存在的原因,也是为什么有时候会发现所删除的层数和自己 docker pull 看到的层数不一样的源。除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。之前讲过,容器是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的。因此该镜像如果被这个容器所依赖的,那么删除必然会导致故障。如果这些容器是不需要的,应该先将它们删除,然后再来删除镜像。
像其它可以承接多个实体的命令一样,可以使用 docker image ls -q 来配合使用 docker image rm ,这样可以成批的删除希望删除的镜像。我们在“镜像列表”章节介绍过很多过滤镜像列表的方式都可以拿过来使用。
比如,我们需要删除所有仓库名为 redis 的镜像:
docker image rm $(docker image ls -q redis)
或者删除所有在 mongo:3.2 之前的镜像:
docker image rm $(docker image ls -q -f before=mongo:3.2)
镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
还以之前定制 nginx 镜像为例,这次我们使用 Dockerfile 来定制。在一个空白目录中,建立一个文本文件,并命名为 Dockerfile :
mkdir mynginx
cd mynginx
touch Dockerfile
其内容为:
FROM nginx
RUN echo ‘Hello, Docker!
’ > /usr/share/nginx/html/index.html
这个 Dockerfile 很简单,一共就两行。涉及到了两条指令, FROM 和 RUN 。然后build镜像
docker build -t mynginx .
使用docker image ls
查看新的镜像
FROM 指定基础镜像
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 nginx 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 FROM 就是指定基础镜像,因此一个Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。 在Docker Hub Container Image Library | App Containerization 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像。
除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch 。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
FROM scratch
…
如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。
RUN 执行命令
RUN 指令是用来执行命令行命令的。由于命令行的强大能力, RUN 指令在定制镜像时是最常用的指令
之一。其格式有两种:
shell 格式: RUN <命令> ,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN
指令就是这种格式。
RUN echo ‘
Hello, Docker!
’ > /usr/share/nginx/html/index.html
exec 格式: RUN [“可执行文件”, “参数1”, “参数2”] ,这更像是函数调用中的格式。
既然 RUN 就像 Shell 脚本一样可以执行命令,那么我们是否就可以像 Shell 脚本一样把每个命令对应一
个 RUN 呢?比如这样:
FROM debian:jessie
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz “http://download.redis.io/releases/redis-3.2.5.tar.gz”
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
之前说过,Dockerfile 中每一个指令都会建立一层, RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后, commit 这一层的修改,构成新的镜像。而上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。这是很多初学 Docker 的人常犯的一个错误。Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。上面的 Dockerfile 正确的写法应该是这样:
FROM debian:jessie
RUN buildDeps=‘gcc libc6-dev make’
&& apt-get update
&& apt-get install -y $buildDeps
&& wget -O redis.tar.gz “http://download.redis.io/releases/redis-
3.2.5.tar.gz”
&& mkdir -p /usr/src/redis
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
&& make -C /usr/src/redis
&& make -C /usr/src/redis install
&& rm -rf /var/lib/apt/lists/*
&& rm redis.tar.gz \&& rm -r /usr/src/redis
&& apt-get purge -y --auto-remove $buildDeps
首先,之前所有的命令只有一个目的,就是编译、安装 redis 可执行文件。因此没有必要建立很多层,这只是一层的事情。因此,这里没有使用很多个 RUN 对一一对应不同的命令,而是仅仅使用一个 RUN指令,并使用 && 将各个所需命令串联起来。将之前的 7 层,简化为了 1 层。在撰写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每一层该如何构建。并且,这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,我们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。
构建镜像
在 Dockerfile 文件所在目录执行:
docker build -t nginx:v3 .
这里我们使用了docker build命令来进行镜像的构建:
docker build [选项] <上下文路径/URL/->
镜像构建上下文(Context)
如果注意,会看到 docker build 命令最后有一个 . 。 . 表示当前目录,而 Dockerfile 就在当前目录,因此不少人以为这个路径是在指定 Dockerfile 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定上下文路径。那么什么是上下文呢?
首先我们要理解 docker build 的工作原理。Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、 ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径, docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
如果在 Dockerfile 中这么写:
COPY ./package.json /app/
这并不是要复制执行 docker build 命令所在的目录下的 package.json ,也不是复制 Dockerfile所在目录下的 package.json ,而是复制 上下文(context) 目录下的 package.json 。
现在就可以理解刚才的命令 docker build -t nginx:v3 . 中的这个 . ,实际上是在指定上下文的目录, docker build 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。
理解构建上下文对于镜像构建是很重要的,避免犯一些不应该的错误。比如有些初学者在发现 COPY/opt/xxxx /app 不工作后,于是干脆将 Dockerfile 放到了硬盘根目录去构建,结果发现 docker build 执行后,在发送一个几十 GB 的东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让docker build 打包整个硬盘,这显然是使用错误。一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore ,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。那么为什么会有人误以为 . 是指定 Dockerfile 所在目录呢?这是因为在默认情况下,如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。这只是默认行为,实际上 Dockerfile 的文件名并不要求必须为 Dockerfile ,而且并不要求必须位于上下文目录中,比如可以用 -f …/Dockerfile.php 参数指定某个文件作为 Dockerfile 。当然,一般大家习惯性的会使用默认的文件名 Dockerfile ,以及会将其置于镜像构建上下文目录中。
其它 docker build 的用法
直接用 Git repo 进行构建
或许你已经注意到了, docker build 还支持从 URL 构建,比如可以直接从 Git repo 中构建:
docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14
用给定的 tar 压缩包构建
docker build http://server/context.tar.gz
如果所给出的 URL 不是个 Git repo,而是个 tar 压缩包,那么 Docker 引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。
从标准输入中读取 Dockerfile 进行构建
docker build - < Dockerfile
或
cat Dockerfile | docker build -
如果标准输入传入的是文本文件,则将其视为 Dockerfile ,并开始构建。这种形式由于直接从标准输入中读取 Dockerfile 的内容,它没有上下文,因此不可以像其他方法那样可以将本地文件 COPY 进镜像之类的事情。
容器是 Docker 三大核心概念之一。如果说Docker镜像是类,则Docker容器就是实例化后的对象,同一个镜像可以启动多个容器。简单的说,容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的,虚拟机可以理解为模拟运行的一整套操作系统(提供了运行态环境和其他系统环境)和跑在上面的应用。
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态( stopped )的容器重新启动。
新建容器: 所需要的命令主要为 docker run 。
sudo docker run -t -i ubuntu:16.04 /bin/bash
其中, -t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开。
在交互模式下,用户可以通过所创建的终端来输入命令。
当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:
启动已终止容器:
可以利用 docker container start 命令,直接将一个已经终止的容器启动运行。
容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。可以在伪终端中利用 ps 或 top 来查看进程信息。
更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d 参数来实现。
容器是否会长久运行,是和 docker run 指定的命令有关,和 -d 参数无关。
使用 -d 参数启动后会返回一个唯一的 id (CONTAINER ID),也可以通过 docker container ls 命
令来查看容器信息。
要获取容器的输出信息,可以通过 docker container logs 命令。
可以使用 docker container stop 来终止一个运行中的容器。
此外,当 Docker 容器中指定的应用终结时,容器也自动终止。
例如对于上一章节中只启动了一个终端的容器,用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建
的容器立刻终止。
注意:容器不退出,返回宿主机 则使用Ctrl+P+Q
终止状态的容器可以用 docker container ls -a 命令看到。
处于终止状态的容器,可以通过 docker container start 命令来重新启动。
此外, docker container restart 命令会将一个运行态的容器终止,然后再重新启动它 。
在使用 -d 参数时,容器启动后会进入后台。
某些时候需要进入容器进行操作,包括使用 docker attach 命令或 docker exec 命令,推荐大家使
用 docker exec 命令,原因会在下面说明 :
docker exec 后边可以跟多个参数,这里主要说明 -i -t 参数。
只用 -i 参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux 命令提示符,但命令执行结果仍然
可以返回。
当 -i -t 参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符。
导出容器
如果要导出本地某个容器,可以使用 docker export 命令。
docker container ls -a
docker export 7691a814370e > ubuntu.tar
这样将导出容器快照到本地文件。
导入容器
可以使用 docker import 从容器快照文件中再导入为镜像,例如
cat ubuntu.tar | docker import - test/ubuntu:v1.0
此外,也可以通过指定 URL 或者某个目录来导入,例如
docker import http://example.com/exampleimage.tgz example/imagerepo
用户既可以使用 _ docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速的部署分布式应用。
Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。从功能上看,跟OpenStack 中的 Heat 十分类似。其代码目前在 https://github.com/docker/compose 上开源。Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」,其前身是开源项目 Fig
我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。
Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件
(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
Compose 中有两个重要的概念:
Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
Compose 项目由 Python 编写,实现上调用了 Docker 服务提供的 API 来对容器进行管理。因此,只要
所操作的平台支持 Docker API,就可以在其上利用 Compose 来进行编排管理。
Compose 支持 Linux、macOS、Windows 10 三大平台。
Compose 可以通过 Python 的包管理工具 pip 进行安装,也可以直接下载编译好的二进制文件使用,甚至能够直接在 Docker 容器中运行。前两种方式是传统方式,适合本地环境下安装使用;最后一种方式则不破坏系统环境,更适合云计算场景。这里我们只介绍二进制安装方式,PIP安装的方式比较麻烦。Docker for Mac 、 Docker for Windows 自带 docker-compose 二进制文件,安装 Docker 之后可以直接使用。
zhen@zhen:/home/wz$ docker-compose -version
docker-compose version 1.25.0, build unknown
在 Linux 上的也安装十分简单,从 官方 https://github.com/docker/compose/releases 处直接下载编译好的二进制文件即可。例如,在 Linux 64 位系统上直接下载对应的二进制包
# 先把docker-compose文件dump到当前目录
wget https://github.com/docker/compose/releases/download/1.27.4/dockercompose-Linux-x86_64
#然后拷贝到/usr/bin/
sudo cp -arf docker-compose-Linux-x86_64 /usr/bin/docker-compose
sudo chmod +x /usr/bin/docker-compose
卸载:
sudo rm /usr/bin/docker-compose
准备
创建一个测试目录:
mkdir composetest
cd composetest
在测试目录中创建一个名为 app.py 的文件,并复制粘贴以下内容:composetest/app.py 文件代码
import time
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)
@app.route('/')
def hello():
count = get_hit_count()
return 'Hello World! I have been seen {} times.\n'.format(count)
在此示例中,redis 是应用程序网络上的 redis 容器的主机名,该主机使用的端口为 6379。
在 composetest 目录中创建另一个名为 requirements.txt 的文件,内容如下:
flask
redis
创建 Dockerfile 文件
在 composetest 目录中,创建一个名为的文件 Dockerfile,内容如下:
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
#RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD ["flask", "run"]
Dockerfile 内容解释:
FROM python:3.7-alpine: 从 Python 3.7 映像开始构建镜像。
WORKDIR /code: 将工作目录设置为 /code。
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
设置 flask 命令使用的环境变量。RUN apk add --no-cache gcc musl-dev linux-headers: 安装 gcc,以便诸如 MarkupSafe 和SQLAlchemy 之类的 Python 包可以编译加速。这里先注释掉,下载太费时间了。
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
复制 requirements.txt 并安装 Python 依赖项。
COPY . .: 将 . 项目中的当前目录复制到 . 镜像中的工作目录。
CMD [“flask”, “run”]: 容器提供默认的执行命令为:flask run。
创建 docker-compose.yml
在测试目录中创建一个名为 docker-compose.yml 的文件,然后粘贴以下内容:
# yaml 配置
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
该 Compose 文件定义了两个服务:web 和 redis。
使用 Compose 命令构建和运行您的应用
在测试目录中,执行以下命令来启动应用程序:
docker-compose up
如果你想在后台执行该服务可以加上 -d 参数:docker-compose up -d