docker入门教程二:镜像与容器

镜像相关操作

镜像从使用上来看,有如下12个指令:

1
2
3
4
5
6
7
8
9
10
11
12
docker image build		Build an image from a Dockerfile
docker image history Show the history of an image
docker image import Import the contents from a tarball to create a filesystem image
docker image inspect Display detailed information on one or more images
docker image load Load an image from a tar archive or STDIN
docker image ls List images
docker image prune Remove unused images
docker image pull Pull an image or a repository from a registry
docker image push Push an image or a repository to a registry
docker image rm Remove one or more images
docker image save Save one or more images to a tar archive (streamed to STDOUT by default)
docker image tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE

在一般情况下,可以省略images,即docker image pull可以简写为docker pull,但也有一些意外,如docker image ls的简写为docker imagesdocker image rm的简写为docker rmi等。

获取镜像docker pull

从 Docker 镜像仓库获取镜像的命令是 docker pull。其命令格式为

1
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]

具体的选项可以通过 docker pull —help 命令看到,这里我们说一下镜像名称的格式。

  • Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。
  • 仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。
  • 如果要下载其他hub上面的镜像,就需要指定域名了,如下载华为的:docker pull swr.cn-north-1.myhuaweicloud.com/fang141x/tank:v1.1

如下为获取一个centos的镜像:

1
2
3
4
5
6
7
8
9
[root@master ~]# docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
35c102085707: Pull complete
251f5509d51d: Pull complete
8e829fe70a46: Pull complete
6001e1789921: Pull complete
Digest: sha256:66cd4dd8aaefc3f19afd407391cda0bc5a0ade546e9819a392d8a4bd5056314e
Status: Downloaded newer image for ubuntu:latest

从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的 sha256 的摘要,以确保下载一致性。

查看镜像docker images

使用docker images 或者 docker image ls来查看,如果要查看特定的镜像,后面直接加镜像名即可。使用-q参数,只显示image id。

1
2
3
4
5
6
7
8
[root@master ~]# docker images centos
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 67fa590cfc1c 3 weeks ago 202MB
[root@master ~]# docker images centos -q
67fa590cfc1c
[root@master ~]# docker images --filter label=maintainer
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 62c261073ecf 3 months ago 109MB

另外我们应该知道镜像ID是镜像的唯一标识,一个镜像可以对应多个标签

可以使用--format参数来指定输出的格式。

1
2
3
4
5
[root@master ~]# docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"
IMAGE ID REPOSITORY TAG
32b4866d70b5 swr.cn-north-1.myhuaweicloud.com/fang141x/2048 v1.1
5fff91fe8881 swr.cn-north-1.myhuaweicloud.com/fang141x/tank v1.1
75835a67d134 centos latest

其他具体详细的用法,请参数官方文档:https://docs.docker.com/engine/reference/commandline/images/ ,或者使用man docker-images来查看帮助信息。

另外一个需要注意的问题是,docker image ls 列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。

你可以通过docker system df命令来便捷的查看镜像、容器、数据卷所占用的空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@master ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 19485c79a9bb 9 days ago 1.22MB
centos latest 67fa590cfc1c 3 weeks ago 202MB
alpine latest 961769676411 3 weeks ago 5.58MB
ubuntu latest a2a15febcdf3 4 weeks ago 64.2MB
nginx latest 62c261073ecf 3 months ago 109MB
[root@master ~]# docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 5 2 382.1MB 267.3MB (69%)
Containers 2 2 130B 0B (0%)
Local Volumes 0 0 0B 0B
Build Cache 0 0 0B 0B

上面可以看到images占用了267M大小,但是docker images显示超过了380M了,这个就说明了docker分层的,同样又是可以复用的。另外Containers一共有2个活跃的,就是有2个在运行,但是占用的大小才130B。

搜索官方提供的带nginx关键字的镜像。official是否是官方的

1
2
3
[root@linjx ~]# docker search --filter is-official=true nginx
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
nginx Official build of Nginx. 10881 [OK]

STARS为收藏数,--filter stars=4是指过滤出收藏数量在4以上的image

1
2
3
4
5
[root@master ~]# docker search busybox --filter stars=4 --limit 3
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
busybox Busybox base image. 1684 [OK]
radial/busyboxplus Full-chain, Internet enabled, busybox made f… 25 [OK]
yauritux/busybox-curl Busybox with CURL 5

Automated是 Docker Hub 提供的功能,他可以在你的 Github repo 有更新时马上根据 Dockerfile 帮你 build 一個新的 image,要使用这個功能首先到 Docker Hub 右上角: Create Automated Build

删除镜像docker rmi

使用docker image rm或者docker rmi来执行。删除image之前必须先删除container。如下,即使容器没有运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@master ~]# docker ps  
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
70a5b4caa8d3 alpine "/bin/sh" 3 minutes ago Up 3 minutes alpine
[root@master ~]# docker stop 70a5b4caa8d3
70a5b4caa8d3
[root@master ~]#
[root@master ~]# docker rmi alpine:latest
Error response from daemon: conflict: unable to remove repository reference "alpine:latest" (must force) - container 70a5b4caa8d3 is using its referenced image 196d12cf6ab1
[root@master ~]# docker rm alpine #删除容器
alpine
[root@master ~]# docker rmi alpine:latest
Untagged: alpine:latest
Untagged: alpine@sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528
Deleted: sha256:196d12cf6ab19273823e700516e98eb1910b03b17840f9d5509f03858484d321

如果观察上面这几个命令的运行输出信息的话,你会注意到删除行为分为两类,一类是 Untagged,另一类是 Deleted。我们之前介绍过,镜像的唯一标识是其 ID 和摘要,而一个镜像可以有多个标签。

因此当我们使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged 的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete 行为就不会发生。所以并非所有的 docker image rm 都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。

当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变动非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。

删除虚悬镜像docker image prune

有时会出现仓库名、标签均为 的镜像。这类无标签镜像也被称为 虚悬镜像(dangling image),可以使用docker image prune删除,前提是容器必须完全退出才能删除。注意,容器处于exited状态也是不能删除虚悬镜像的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@master ~]# docker image ls -f dangling=true
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 196d12cf6ab1 2 months ago 4.41M
[root@master ~]# docker ps -a | grep "Exited"
02c1ff405078 196d12cf6ab1 "sh" 2 days ago Exited (137) 2 days ago tender_cocks
[root@master ~]# docker rm 02c1ff405078
02c1ff405078
[root@master ~]#
[root@master ~]# docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Deleted Images:
deleted: sha256:196d12cf6ab19273823e700516e98eb1910b03b17840f9d5509f03858484d321

Total reclaimed space: 0B
[root@master ~]# docker image ls -f dangling=true
REPOSITORY TAG IMAGE ID CREATED SIZE

添加标签docker tag

使用docker [image] tag来重命名一个标签,非常有用。完整用法是docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG],这跟 cp 命令是一样,先源后位置。

1
2
3
4
5
6
7
8
9
10
[root@master ~]# docker images *alpine
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest 961769676411 3 weeks ago 5.58MB
[root@master ~]# docker tag alpine:latest myalpine:latest
[root@master ~]# docker images *alpine
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest 961769676411 3 weeks ago 5.58MB
myalpine latest 961769676411 3 weeks ago 5.58MB
[root@master ~]# docker rmi myalpine:latest
Untagged: myalpine:latest

使用tag有点类似做了一个软链接,但是跟软链接还是有区别的,删除myalpine:latest只是解除了tag名字。如果再去删除alpine,就是真正删除了这个images了。

查看详细信息docker inspect

使用docker [image] inspect命令可以获取该镜像的详细信息,包括制作者、适应架构、各层的数字摘要等。此命令也同样适用于容器、网络、数据卷等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 原始内容
[root@master ~]# docker inspect nginx-web |sed -n '/State/,/}/p'
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 24481,
"ExitCode": 0,
"Error": "",
"StartedAt": "2019-08-17T13:52:12.669876272Z",
"FinishedAt": "2019-08-14T14:44:45.108948524Z"
},
# 只显示State字段内容
[root@master ~]# docker inspect -f '{{.State}}' nginx-web
{running true false false false false 24481 0 2019-08-17T13:52:12.669876272Z 2019-08-14T14:44:45.108948524Z <nil>}
# 以json格式输出
[root@master ~]# docker inspect -f '{{json .State}}' nginx-web
{"Status":"running","Running":true,"Paused":false,"Restarting":false,"OOMKilled":false,"Dead":false,"Pid":24481,"ExitCode":0,"Error":"","StartedAt":"2019-08-17T13:52:12.669876272Z","FinishedAt":"2019-08-14T14:44:45.108948524Z"}
# 使用jq再来格式化一次
[root@master ~]# docker inspect -f '{{json .State}}' nginx-web |jq
{
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 24481,
"ExitCode": 0,
"Error": "",
"StartedAt": "2019-08-17T13:52:12.669876272Z",
"FinishedAt": "2019-08-14T14:44:45.108948524Z"
}

其他用法可以参考:奇妙的 Docker Inspect 模版

查看历史docker history

使用docker [image] history查看,显示的是镜像的构建时使用了哪些操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@master ~]# docker history nginx:latest
IMAGE CREATED CREATED BY SIZE COMMENT
62c261073ecf 3 months ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 3 months ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B
<missing> 3 months ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 3 months ago /bin/sh -c ln -sf /dev/stdout /var/log/nginx… 22B
<missing> 3 months ago /bin/sh -c set -x && addgroup --system -… 54MB
<missing> 3 months ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~stretch 0B
<missing> 3 months ago /bin/sh -c #(nop) ENV NJS_VERSION=0.3.2 0B
<missing> 3 months ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.17.0 0B
<missing> 4 months ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 4 months ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 4 months ago /bin/sh -c #(nop) ADD file:fcb9328ea4c115670… 55.3MB

如上所示,CREATED BY是有截断的,看不出来完整的命令,可以使用如下指令显示完整信息:

1
2
3
4
5
6
7
8
9
10
11
12
[root@master ~]# docker history nginx:latest --no-trunc=true --format "{{.ID}}: {{.CreatedBy}}"
sha256:62c261073ecffe22a28f2ba67760a9320bc4bfe8136a83ba9b579983346564be: /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"]
<missing>: /bin/sh -c #(nop) STOPSIGNAL SIGTERM
<missing>: /bin/sh -c #(nop) EXPOSE 80
<missing>: /bin/sh -c ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log
<missing>: /bin/sh -c set -x && addgroup --system --gid 101 nginx && adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid101 nginx && apt-get update && apt-get install --no-install-recommends --no-install-suggests -y gnupg1 apt-transport-https ca-certificates && NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; found=''; for server in ha.pool.sks-keyservers.net hkp://keyserver.ubuntu.com:80 hkp://p80.pool.sks-keyservers.net:80 pgp.mit.edu ; do echo "Fetching GPG key $NGINX_GPGKEY from $server"; apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; done; test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; apt-get remove--purge --auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* && dpkgArch="$(dpkg --print-architecture)" && nginxPackages=" nginx=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-${PKG_RELEASE} " && case "$dpkgArch" in amd64|i386) echo "deb https://nginx.org/packages/mainline/debian/ stretch nginx" >> /etc/apt/sources.list.d/nginx.list && apt-get update ;; *) echo "deb-src https://nginx.org/packages/mainline/debian/ stretch nginx" >> /etc/apt/sources.list.d/nginx.list && tempDir="$(mktemp -d)" && chmod 777 "$tempDir" && savedAptMark="$(apt-mark showmanual)" && apt-get update && apt-get build-dep -y $nginxPackages && ( cd "$tempDir" && DEB_BUILD_OPTIONS="nocheck parallel=$(nproc)" apt-get source --compile $nginxPackages ) && apt-mark showmanual | xargs apt-mark auto > /dev/null && { [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; } && ls -lAFh "$tempDir" && ( cd "$tempDir" && dpkg-scanpackages . > Packages ) && grep '^Package: ' "$tempDir/Packages"&& echo "deb [ trusted=yes ] file://$tempDir ./" > /etc/apt/sources.list.d/temp.list && apt-get -o Acquire::GzipIndexes=false update ;; esac && apt-get install --no-install-recommends --no-install-suggests -y $nginxPackages gettext-base && apt-get remove --purge --auto-remove -y apt-transport-https ca-certificates && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list && if [ -n "$tempDir" ]; then apt-get purge -y --auto-remove && rm -rf "$tempDir" /etc/apt/sources.list.d/temp.list; fi
<missing>: /bin/sh -c #(nop) ENV PKG_RELEASE=1~stretch
<missing>: /bin/sh -c #(nop) ENV NJS_VERSION=0.3.2
<missing>: /bin/sh -c #(nop) ENV NGINX_VERSION=1.17.0
<missing>: /bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>
<missing>: /bin/sh -c #(nop) CMD ["bash"]
<missing>: /bin/sh -c #(nop) ADD file:fcb9328ea4c1156709f3d04c3d9a5f3667e77fb36a4a83390ae2495555fc0238 in /

保存镜像docker save

使用docker [image] save image_name -o tar_name来保存已有的镜像。这个就可以将镜像分享给其他人。

1
2
3
4
5
6
[root@master ~]# docker images nginx
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 62c261073ecf 3 months ago 109MB
[root@master ~]# docker save nginx -o nginx.tar
[root@master ~]# ll -h nginx.tar
-rw------- 1 root root 108M Sep 14 23:07 nginx.tar

导入镜像docker load

使用docker load -i tar_name或者docker load < tar_name导入镜像。导人成功后,可以使用docker images 命令进行查看, 与原镜像一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@master ~]# docker save busybox -o busybox.tar.gz
[root@master ~]# ll -h busybox.tar.gz
-rw------- 1 root root 1.4M Sep 14 23:19 busybox.tar.gz
[root@master ~]# docker rmi busybox:latest
Untagged: busybox:latest
Untagged: busybox@sha256:be38efcd3a8289ff66e60c5652beed36968eeb856ae5d90f3cf02ea81e4a57da
Deleted: sha256:19485c79a9bbdca205fce4f791efeaa2a103e23431434696cc54fdd939e9198d
Deleted: sha256:6c0ea40aef9d2795f922f4e8642f0cd9ffb9404e6f3214693a1fd45489f38b44
[root@master ~]#
[root@master ~]# docker load < busybox.tar.gz
6c0ea40aef9d: Loading layer [==================================================>] 1.437MB/1.437MB
Loaded image: busybox:latest
[root@master ~]# docker images busybox
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 19485c79a9bb 9 days ago 1.22MB

创建镜像

创建镜像的方法主要有三种: 基于已有镜像的容器创建、基千本地模板导入、基于Dockerfile 创建。

基于容器创建

使用docker [container] commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]来创建,-m表示message,-a作者信息,-p提交时暂停容器运行。

1
2
3
4
5
6
7
8
[root@master ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5e77d1753daa alpine "/bin/sh" 2 days ago Up 2 days alpine
[root@master ~]# docker commit -m "this is a test" -a "fangdm" -p alpine test:v1
sha256:b7aa78bcbfe1455640ce89545e52f22eacf1f3529103347fbbc1d69209e67388
[root@master ~]# docker images test
REPOSITORY TAG IMAGE ID CREATED SIZE
test v1 b7aa78bcbfe1 11 seconds ago 5.58MB

使用 docker commit 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中并不会这样使用。由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,如果不小心清理,将会导致镜像极为臃肿。

其他用法,请参考https://docs.docker.com/engine/reference/commandline/commit/

基于容器导入

这个意思是说将容器导出之后,形成一下没压缩包,将这个压缩包通过docker import来导入,具体的用法为:docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]

1
2
3
4
5
6
7
8
9
10
[root@master ~]# docker export alpine >alpine.tar
[root@master ~]# ll -h alpine.tar
-rw-r--r-- 1 root root 5.6M Sep 16 22:57 alpine.tar
[root@master ~]# docker import alpine.tar ^C
[root@master ~]# cat alpine.tar |docker import - test:v2
sha256:d36b315f1b2c708f3f6becb7f396fe1e14d46cccfdddda00504a2e1d0df377c9
[root@master ~]# docker images test
REPOSITORY TAG IMAGE ID CREATED SIZE
test v2 d36b315f1b2c 9 seconds ago 5.58MB
test v1 b7aa78bcbfe1 13 minutes ago 5.58MB

镜像保存/载入:docker load/docker save;将一个镜像导出为文件,再使用docker load命令将文件导入为一个镜像,会保存该镜像的的所有历史记录。比docker export命令导出的文件大,很好理解,因为会保存镜像的所有历史记录。容器导入/导出:docker import/docker export;将一个容器导出为文件,再使用docker import命令将容器导入成为一个新的镜像,但是相比docker save命令,容器文件会丢失所有元数据和历史记录,仅保存容器当时的状态,相当于虚拟机快照。

如下是docker save与docker import的区别,可以看到import丢失了所有的历史记录

1
2
3
4
5
6
7
8
[root@master ~]# docker history test:v1
IMAGE CREATED CREATED BY SIZE COMMENT
b7aa78bcbfe1 20 hours ago /bin/sh 0B this is a test
961769676411 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:fe64057fbb83dccb9… 5.58MB
[root@master ~]# docker history test:v2
IMAGE CREATED CREATED BY SIZE COMMENT
d36b315f1b2c 20 hours ago 5.58MB Imported from -

Dockerfile创建

这是镜像创建最常见的方法。Dockerfile是一个文本文件,利用给定的指令描述基于某个父镜像创建新镜像的过程。创建一个Dockerfile文件,然后使用docker build -t Name:version即可。此部分以后再详细介绍 。

上传镜像docker push

可以使用docker [image] push命令上传镜像到仓库,默认上传到Docker Hub 官方仓库(需要登录)。

官方登陆方法

先打开 https://hub.docker.com 进行注册,完成之后,直接使用docker logint进行登陆。

1
2
3
4
5
6
7
8
9
[root@master ~]# docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: 用户名
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

登录成功之后,根据提示信息可以知道,它把用户名密码存放在/root/.docker/config.json中,进入查看:

1
2
3
4
5
6
7
8
9
10
11
[root@master ~]# cat /root/.docker/config.json
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "xxxx"
}
},
"HttpHeaders": {
"User-Agent": "Docker-Client/18.09.6 (linux)"
}
}

这个auth是通过base64进行加密的,可以使用echo 'auth中的内容'|base64 --decode进行解密,解码后即是username:password。

推送镜像至官方

使用docker push [OPTIONS] NAME[:TAG]推送,但是要注意的是要加上刚刚注册的用户名,如果不加上,就默认是推送到官方的仓库中,即library,是没有权限的,直接会报denied错误。需要使用docker tag image username/repository:tag来重命名。

1
2
3
4
5
6
7
8
9
10
fang2000:sghje141x[root@master ~]# docker push  test:v1
The push refers to repository [docker.io/library/test]
03901b4a2ea8: Preparing
denied: requested access to the resource is denied
[root@master ~]#
[root@master ~]# docker tag test:v1 fang2000/test:v1
[root@master ~]# docker push fang2000/test:v1
The push refers to repository [docker.io/fang2000/test]
03901b4a2ea8: Pushed
v1: digest: sha256:e93947c44129c35ea38015906c1005fe597e6c631da53a22dbafa7c0b8cf7667 size: 528

推送成功之后,登陆hub就可以看到上传好的镜像了。

详细的用法,请参考:Docker镜像推送(push)到Docker Hub

容器相关操作

容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的,虚拟机可以理解为模拟运行的一整套操作系统(提供了运行态环境和其他系统环境)和跑在上面的应用。跟容器有关的操作指令有很多,可以查看官方文档: https://docs.docker.com/engine/reference/commandline/container/ ,主要的指令如下:

  • docker run 运行container
  • docker stop 停止运行的container
  • docker start 运行已经停止运行的container
  • docker rm 删除container
  • docker attach 进入container
  • docker exec 也是进入container,区别下面介绍
  • docker export 导出container
  • docker import 导入container,这在镜像时有介绍
  • docker inspect 查看container信息
  • docker top 查看container内的进程
  • docker logs 查看container的日志
  • docker port 查看container内的端口映射
  • docker stats 查看container内的统计信息
  • docker cp 复制文件,支持container与主机之后相互复件文件
  • docker diff 查看container内有变更过的文件
  • docker update 更新container运行时的配置,主要是资源限制份额的更新

运行容器

docker run介绍

使用docker run命令来启动容器,运行后,docker会做以下操作::

  1. 检查本地是否存在指定的镜像,不存在则从公有仓库下载
  2. 使用镜像创建并启动容器
  3. 分配一个文件系统,并在只读的镜像层外面挂载一层可读可写层
  4. 从宿主主机配置的网桥接口中桥接一个虚拟接口道容器中去
  5. 从地址池分配一个ip地址给容器
  6. 执行用户指定的应用程序

docker run参数

完整的命令为docker run [OPTIONS] IMAGE [COMMAND] [ARG...],其中OPTIONS为参数,有很多,下面会介绍;IMAGE就是镜像的名字,后面接的是[COMMAND],说明这个参数可有可无,如果不指定,会默认运行Dockerfile指定的命令。

以下是常用参数:

  • -d: 后台运行容器,并返回容器ID
  • -i:以交互模式运行容器,通常与 -t 同时使用
  • -t:为容器重新分配一个伪输入终端
  • -p:端口映射,格式为:主机(宿主)端口:容器端口
  • -m:设置容器使用内存最大值
  • -v:给容器挂载存储卷,挂载到容器的某个目录
  • —name=”nginx-lb”: 为容器指定一个名称,后续可以通过名字进行容器管理,links特性需要使用名字
  • —rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。
  • —net: 指定容器的网络连接类型,支持 bridge/host/none/container四种类型;
    • —net=bridge: 使用docker daemon指定的网桥
    • —net=host: 容器使用主机的网络
    • —net=container:NAME_or_ID:使用其他容器的网路,共享IP和PORT等网络资源
    • —net=none:容器使用自己的网络(类似—net=bridge),但是不进行配置

简单实例

运行ubuntu镜像,这里使用了--rm表示可以使用exit直接退出运行,当容器退出后随之会删除。

1
2
3
4
5
6
7
8
[root@master ~]# docker run -it --rm --name ubuntu ubuntu
root@1d3371e7cf31:/# cat /etc/issue
Ubuntu 18.04.3 LTS \n \l

root@1d3371e7cf31:/# exit
exit
[root@master ~]# docker ps -a |grep ubuntu
[root@master ~]#

但是可以使用ctrl+p,ctrl+q返回宿主机。

1
2
3
4
5
6
7
8
9
10
[root@master ~]# docker run -it --rm --name ubuntu ubuntu
root@6277c57f8d2c:/#
root@6277c57f8d2c:/# [root@master ~]#
[root@master ~]# docker ps -a |grep ubuntu
6277c57f8d2c ubuntu "/bin/bash" 15 seconds ago Up 11 seconds ubuntu
#进入容器
[root@master ~]# docker attach ubuntu
root@6277c57f8d2c:/# w
14:15:59 up 87 days, 23:06, 0 users, load average: 0.22, 0.14, 0.10
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT

以下命令是运行tank镜像,使用本地的default.conf配置文件,将宿主机的端口8081映射到容器的8080端口上。

1
docker run -d --name tank1 -p 8081:8080 -v $PWD/default.conf://etc/nginx/conf.d/default.conf swr.cn-north-1.myhuaweicloud.com/fang141x/tank:v1.1

挂载目录

Docker容器启动的时候,如果要挂载宿主机的一个目录,可以用-v参数指定。如下,使用-v /test:/soft就可以实现,冒号”:”前面的目录是宿主机目录,后面的目录是容器内目录。如果目录不存在,则会自动创建目录。

1
2
3
4
5
6
7
8
[root@master ~]# ll /test
ls: cannot access /test: No such file or directory
[root@master ~]# docker run -it --rm -v /test:/soft alpine
/ # ls -l /soft/
total 0
/ # exit
[root@master ~]# ll /test
total 0

两个目录是有一些的区别的,容器目录不可以为相对路径,但是宿主机是可以使用相对路径的。不过挂载时不是当前的目录了。如下,使用-v test:/soft之后,test目录是挂载到/var/lib/docker/volumes/test这里面去了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@master ~]# docker run -itd --rm -v test:/soft alpine
8bbd2d5472bbb81e6c82e6814d62cdb375c6a70ec0a578b081fc2c279b924b88
[root@master ~]# docker inspect -f '{{json .Mounts}}' modest_kare |jq
[
{
"Type": "volume",
"Name": "test",
"Source": "/var/lib/docker/volumes/test/_data",
"Destination": "/soft",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
]

如果只是-v指定一个目录,那表示绑定宿主机的一个随机目录至容器的指定的目录下。

使用-v绑定之后,在容器里面修改目录的权限以及属主,是会同步至宿主机中的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@master ~]# chmod 755 /test
[root@master ~]# ls -ld /test
drwxr-xr-x 2 root root 6 Sep 20 22:27 /test
[root@master ~]#
[root@master ~]#
[root@master ~]# docker run -it --name mount -v /test:/soft centos
[root@414c95ff493d /]# ls -ld /soft/
drwxr-xr-x 2 root root 6 Sep 20 14:27 /soft/
[root@414c95ff493d /]# chmod 644 /soft/
[root@414c95ff493d /]# useradd admin
[root@414c95ff493d /]# chown admin:admin /soft/
[root@414c95ff493d /]# ls -ld /soft/
drw-r--r-- 2 admin admin 6 Sep 20 14:27 /soft/
[root@414c95ff493d /]# exit
exit
[root@master ~]# docker rm mount
mount
[root@master ~]# ls -ld /test
drw-r--r-- 2 1000 1000 6 Sep 20 22:27 /test

从这个例子中,可以看出在容器里面修改目录的权限以及属主,是会同步至宿主机中的。同时,容器销毁之后,目录权限还是保持原样。来源:关于Docker目录挂载的总结

容器的重启策略

Docker容器的重启都是由Docker守护进程完成的,因此与守护进程息息相关。

Docker容器的重启策略如下:

  • no,默认策略,在容器退出时不重启容器
  • on-failure,在容器非正常退出时(退出状态非0),才会重启容器
  • on-failure:3,在容器非正常退出时重启容器,最多重启3次
  • always,在容器退出时总是重启容器
  • unless-stopped,在容器退出时总是重启容器,但是不考虑在Docker守护进程启动时就已经停止了的容器

docker run的退出状态码如下:

  • 0,表示正常退出
  • 非0,表示异常退出(退出状态码采用chroot标准)
  • 125,Docker守护进程本身的错误
  • 126,容器启动后,要执行的默认命令无法调用
  • 127,容器启动后,要执行的默认命令不存在
  • 其他命令状态码,容器启动后正常执行命令,退出命令时该命令的返回状态码作为容器的退出状态码

通过—restart选项,可以设置容器的重启策略,以决定在容器退出时Docker守护进程是否重启刚刚退出的容器。-restart选项通常只用于detached模式的容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 启动一个容器,没有加--restart选项
[root@master ~]# docker run -itd --name tank -p 8080:80 tank
4d14a9c701b7fb9f68dad8f77c0cfa0976cf5c75167b398fa7e56fdd42188f3e
[root@master ~]# docker inspect -f '{{json .HostConfig.RestartPolicy}}' tank
{"Name":"no","MaximumRetryCount":0}
[root@master ~]#
#更新restart策略
[root@master ~]# docker update --restart always tank
tank
[root@master ~]# docker inspect -f '{{json .HostConfig.RestartPolicy}}' tank
{"Name":"always","MaximumRetryCount":0}

#模拟异常退出,先查看进程号
[root@master ~]# docker top tank
UID PID PPID C STIME TTY TIME CMD
root 4668 4650 0 08:52 pts/0 00:00:00 nginx: master process nginx -gdaemon off;
101 4702 4668 0 08:52 pts/0 00:00:00 nginx: worker proces
[root@master ~]# kill -9 4668
[root@master ~]# docker top tank
UID PID PPID C STIME TTY TIME CMD
root 4935 4918 9 08:54 pts/0 00:00:00 nginx: master process nginx -gdaemon off;
101 4972 4935 0 08:54 pts/0 00:00:00 nginx: worker process
#查看重启的次数
[root@master ~]# docker inspect -f "{{ .RestartCount }}" tank
1

如上所述,直接kill掉进程后之后,进程的ID是有发现变化的,所以策略有生效了。但是如果直接使用docker stop的话,容器就不会起来。这点要特别注意。官方还有另外一个实例:https://docs.docker.com/engine/reference/run/#restart-policies---restart

其他参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]  
-a, --attach=[] 登录容器(以docker run -d启动的容器)
-c, --cpu-shares=0 设置容器CPU权重,在CPU共享场景使用
--cap-add=[] 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cap-drop=[] 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cidfile="" 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
--cpuset="" 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
--device=[] 添加主机设备给容器,相当于设备直通
--dns=[] 指定容器的dns服务器
--dns-search=[] 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
-e, --env=[] 指定环境变量,容器中可以使用该环境变量
--entrypoint="" 覆盖image的入口点
--env-file=[] 指定环境变量文件,文件格式为每行一个环境变量
--expose=[] 指定容器暴露的端口,即修改镜像的暴露端口
-h, --hostname="" 指定容器的主机名
--link=[] 指定容器间的关联,使用其他容器的IP、env等信息
--lxc-conf=[] 指定容器的配置文件,只有在指定--exec-driver=lxc时使用
--privileged=false 指定容器是否为特权容器,特权容器拥有所有的capabilities
--restart="" 指定容器停止后的重启策略,待详述
--sig-proxy=true 设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理
-u, --user="" 指定容器的用户
--volumes-from=[] 给容器挂载其他容器上的卷,挂载到容器的某个目录
-w, --workdir="" 指定容器的工作目录

容器在使用的时候,基本上都是在跟docker run打交道,比较熟记,官网简介:https://docs.docker.com/engine/reference/commandline/run/

复制文件docker cp

此命令的作用是在容器和本地文件系统之间复制文件/文件夹。从帮助信息可以看出,docker cp后接container的名字就是从容器复制文件到宿主机。也就是说第一个参数是源位置,指从哪里复制文件或目录;第二个参数为目的位置,要把文件或目录复制到哪里。并且不管容器有没有启动,拷贝命令都会生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# docker cp --help

Usage: docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
从容器中复制文件或目录到执行命令所在机器的指定路径
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH
从执行命令所在的机器复制文件或目录到容器内部的指定路径

Copy files/folders between a container and the local filesystem
在容器和本地文件系统之间复制文件/文件夹

Options:
-a, --archive Archive mode (copy all uid/gid information)
归档模式(复制所有UID / GID信息)
-L, --follow-link Always follow symbol link in SRC_PATH
总是跟在源地址符号链接

docker cp命令类似于UNIX中的cp -a命令,递归复制目录下的所有子目录和文件,如下是一个从容器里面复制目录到宿主机的当前目录。

1
2
3
4
5
6
7
8
[root@master ~]# docker cp tank:/usr/share/nginx/html .
[root@master ~]# ll html/
total 28
-rw-r--r-- 1 root root 494 Oct 2 2018 50x.html
drwxr-xr-x 3 root root 40 Dec 5 2018 data
drwxr-xr-x 3 root root 69 Dec 5 2018 demos
-rw-r--r-- 1 root root 41 Dec 5 2018 Dockerfile
...

拓展一下,其实容器里面的目录和文件都是在保存在Docker Root Dir这里面的,在使用overlay2存储结构下,可以在MergedDir下面找到所有的目录和文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@master html]# docker info |grep 'Docker Root Dir'
Docker Root Dir: /var/lib/docker
[root@master html]# docker inspect -f '{{json .GraphDriver.Data}}' tank |jq
{
"LowerDir": "/var/lib/docker/overlay2/aa1b6b2d3be4e1e0cc1021b42b2e32dd5f72c9bf4212b7653882f4aba92ddddb-init/diff:/var/lib/docker/overlay2/acb898e6e8c9943eb2e781b4e6e72af29f657d956af99b11a8c09e6d2f4fa01e/diff:/var/lib/docker/overlay2/87067ffeb1972861776b7d012ef8d7654200d06934db3ca4d29914bc8983b45b/diff:/var/lib/docker/overlay2/be6e1ebc512fc68100c12884dc850d357b0a32406525c7364946bdfe3046dd0b/diff:/var/lib/docker/overlay2/93da3d426ee7569814eb7138bcef86476cfe3bf83f0f80e244dfc99ce586a6ca/diff",
"MergedDir": "/var/lib/docker/overlay2/aa1b6b2d3be4e1e0cc1021b42b2e32dd5f72c9bf4212b7653882f4aba92ddddb/merged",
"UpperDir": "/var/lib/docker/overlay2/aa1b6b2d3be4e1e0cc1021b42b2e32dd5f72c9bf4212b7653882f4aba92ddddb/diff",
"WorkDir": "/var/lib/docker/overlay2/aa1b6b2d3be4e1e0cc1021b42b2e32dd5f72c9bf4212b7653882f4aba92ddddb/work"
}
[root@master html]# pwd
/var/lib/docker/overlay2/aa1b6b2d3be4e1e0cc1021b42b2e32dd5f72c9bf4212b7653882f4aba92ddddb/merged/usr/share/nginx/html
[root@master html]# ll
total 28
-rw-r--r-- 1 root root 494 Oct 2 2018 50x.html
drwxr-xr-x 3 root root 40 Dec 5 2018 data
drwxr-xr-x 3 root root 69 Dec 5 2018 demos
-rw-r--r-- 1 root root 41 Dec 5 2018 Dockerfile
。。。

进入容器方法

在运行容器之后,我们有时需要进入容器去查看文件、日志、调试、启动其他进程等等一些信息,所以就有了以下方法。

docker attach

通过 docker attach 可以 attach 到容器启动命令的终端。允许多个用户同时attach,但是每个人操作的内容都会同步显示,如下图,左边是我操作的内容,右边就会同时显示这些内容。同时还有一个特点,如果使用exit退出之后,容器也相应地停止运行了。所以其选项基本上不会用于生产环境。

注:可通过 Ctrl+p 然后 Ctrl+q 组合键退出 attach 终端。这样容器就还会继续 运行。

docker exec

生产环境下一般是使用这个命令来进入。完整的命令是docker exec [OPTIONS] CONTAINER COMMAND [ARG...],这跟docker run的用法差不多,要进入容器,就是要运行容器里面的bash命令。主要是需要理解 COMMAND [ARG...],这个ARG就是command的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 以下命令仅是使用容器里面的bash命令运行echo 123
[root@master ~]# docker exec tank bash -c "echo 123"
123
# 进入容器,必须要使用-it参数,分配tty资源
[root@master ~]# docker exec tank bash
# 进入容器的正确方法
[root@master ~]# docker exec -it tank bash
root@4d14a9c701b7:/#
root@4d14a9c701b7:/# exit
exit
# 实例:重启加载nginx的配置文件
[root@master ~]# docker exec tank nginx -s reload
2019/09/21 02:08:14 [notice] 38#38: signal process started

attach 与 exec 主要区别如下:

  • attach 直接进入容器 启动命令 的终端,不会启动新的进程。
  • exec 则是在容器中打开新的终端,并且可以启动新的进程。
  • 如果想直接在终端中查看启动命令的输出,用 attach;其他情况使用 exec。当然,如果只是为了查看启动命令的输出,可以使用 docker logs 命令。

nsenter

nsenter跟docker exec类似,但nsenter不进入cgroups,因此可以避免资源限制。这样做的潜在好处是调试和外部审计。具体请参考:https://github.com/jpetazzo/nsenter

安装

在了解了什么是 nsenter 之后,我们需要把 nsenter 安装到主机中(注意:是主机而非容器或镜像)

1
2
3
4
5
6
7
[root@master ~]# docker pull jpetazzo/nsenter
Using default tag: latest
。。。
[root@master ~]# docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter
Installing nsenter to /target
Installing docker-enter to /target
Installing importenv to /target

这样运行完成之后,nsenter就安装到了/usr/local/bin下了。他是怎么样实现的是呢?使用docker history --no-trunc jpetazzo/nsenter:latest |grep CMD可以看到,CMD ["/bin/sh" "-c" "/installer"]运行的是/installer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@master ~]# docker run --rm -it jpetazzo/nsenter bash
root@c73eb92d3d7a:/src# cat /installer
#!/bin/sh
if mountpoint -q /target; then
echo "Installing nsenter to /target"
cp /nsenter /target
echo "Installing docker-enter to /target"
cp /docker-enter /target
echo "Installing importenv to /target"
cp /importenv /target
else
echo "/target is not a mountpoint."
echo "You can either:"
echo "- re-run this container with -v /usr/local/bin:/target"
echo "- extract the nsenter binary (located at /nsenter)"
fi

就是复制nsenter至/target目录下,这样就完成了命令的安装了。

还有另外一个安装方法:先下载包:https://mirrors.edge.kernel.org/pub/linux/utils/util-linux/ ,然后使用./configure --without-ncurses ; cp nsenter /usr/local/bin也是可以的。

使用方法

在使用nsenter命令之前需要获取到docker容器的进程,然后再使用nsenter工具进去到docker容器中,具体的使用方法如下:

1
2
$ docker inspect -f {{.State.Pid}} 容器名或者容器id   #每一个容器都有.State.Pid,所以这个命令除了容器的id需要我们根据docker ps -a去查找,其他的全部为固定的格式
$ nsenter --target 上面查到的进程id --mount --uts --ipc --net --pid #输入该命令便进入到容器中

在安装完nsenter,还会附加docker-enter,这个命令只是一个shell脚本,将以上2步操作合并为一个。如下,docker-enter后面容器名或者ID即可。

1
2
3
[root@master ~]# docker-enter tank
root@4d14a9c701b7:/# w
bash: w: command not found

另外,有些情况下还可以调用宿主机的命令:

1
2
3
4
5
6
7
8
9
10
11
[root@master share]# docker-enter tank
root@4d14a9c701b7:/# lsof -i
bash: lsof: command not found
root@4d14a9c701b7:/# exit
exit
[root@master share]# nsenter --target $PID --net -- lsof -i
lsof: no pwd entry for UID 101
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 4935 root 6u IPv4 679924 0t0 TCP *:http (LISTEN)
lsof: no pwd entry for UID 101
nginx 9466 101 6u IPv4 679924 0t0 TCP *:http (LISTEN)

以上内容有大量参考了网上博文,同时对每个命令都有实际的操作记录,如有侵权,请及时联系我。万里长征,这只是第一步,仅仅是到image和container,就写了近一周时间,还需要继续努力 。

0%