运行容器示例

这是前一篇的内容,可以先来练习回顾一下容器的操作。

Nginx

直接下载镜像并启动容器,这里选择alpine版的:

$ docker run --name web1 -p 8001:80 -d nginx:alpine
Unable to find image 'nginx:alpine' locally
alpine: Pulling from library/nginx
e7c96db7181b: Downloading 
3fb6217217ef: Download complete 
alpine: Pulling from library/nginx
e7c96db7181b: Pull complete 
3fb6217217ef: Pull complete 
Digest: sha256:17bd1698318e9c0f9ba2c5ed49f53d690684dab7fe3e8019b855c352528d57be
Status: Downloaded newer image for nginx:alpine
01c17a72e943e93d71b56b433bea7a3d6ffa1f848dc3947f2adaf2bb2e3e7fee
$

启动参数说明:

  • -d,表示启动容器后在宿主机的后台运行。
  • -p,端口映射,将宿主机的8001端口,映射到容器内部的80端口。端口映射是network的内容,之后会详细说明。

查看启动的容器:

$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
f57cd5f9d50f        nginx:alpine        "nginx -g 'daemon of…"   13 minutes ago      Up 12 minutes       0.0.0.0:8001->80/tcp   web1
$ 

可以看到端口和端口映射的情况。
这里主要关注COMMAND,上面的显示被截断了:

$ docker container ls --no-trunc --format '{{.Command}}'
"nginx -g 'daemon off;'"
$ 

这里启动nginx加了参数,daemon off 字面的意思就是关闭守护进程。这是为了让nginx在前台运行。
如果是nginx默认的启动方式,那么nginx程序将在后台运行,一旦nginx启动完就没有任何程序了,结果容器也就退出了。

在容器中执行任何程序或服务,一定不能在容器中运行在后台。只要运行在后台,一启动就会终止。

既然Nginx已经启动,就可以直接用浏览器访问了。并且做了端口映射,所以可以直接通过宿主机的端口来进行访问:http://[宿主机的IP地址]:8001

容器的日志
每一个容器的目的只是为了运行一个程序,这个程序就是容器的主进程PID=1。传统的程序的日志一般是保存在日志文件中的,但是容器中没有这个必要。因为现在整个容器就只为了运行一个进程,日志就可以直接打印在控制台上了,就是程序直接在前台运行的效果。
使用下面的命令可以查看日志:

$ docker container logs web1

查看后,访问几次页面再看下是否有访问日志刷新。

redis

首先,直接启动一个redis:

$ docker container run --name redis -d redis:alpine

容器启动后,依然是停留在宿主机的命令行界面。

进入容器内部
现在需要进入到容器内部进行操作,就像之前的busybox那样。但是,这次容量内部运行的是一个 redis-server 的程序,并且一个容器内部一般只运行一个程序。所以容器里并没有shell。
这里和之前的busybox容器的情况不同,在busybox容器内部就有一个shell。所以直接进入是没有任何终端界面的。这里需要启动一个shell然后进入:

$ docker container exec -it redis /bin/sh
/data # ps
PID   USER     TIME  COMMAND
    1 redis     0:00 redis-server
   12 root      0:00 /bin/sh
   23 root      0:00 ps
/data # 

进入并且执行命令查看当前容器内的进程。
这里看到,除了ps命令,还有原本的 redis-server 以及进入容器时启动的shell。所以在容器内部运行多个进程也是可以的,现在就是这个情况。不过一般也就这在这种场景下需要在容器中运行多个进程。

执行其他操作
既然都进来了,就运行些命令。看下系统的时间:

/data # date
Tue Jul 16 13:03:05 UTC 2019
/data #

时间没问题,不过时区不对,这个略过。

查看端口监听情况:

/data # netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      
tcp        0      0 :::6379                 :::*                    LISTEN      
/data # 

使用 redis-cli 命令:

/data # redis-cli
127.0.0.1:6379> set age 23
OK
127.0.0.1:6379> set name Adam
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> exit
/data # exit
$ 

这里看到 redis-cli 自带用户界面,所以不用启动 /bin/sh 也能直接进来:

$ docker container exec -it redis redis-cli
127.0.0.1:6379> exit
$ 

Docker 镜像

Docker镜像的基础知识。

镜像启动

docker镜像含有启动容器所需要的文件系统及其内容,因此,其用于创建并启动docker容器。
采用分层构建机制,最底层为bootfs,次之为rootfs:

  • bootfs: 用于系统引导的文件系统,包括bootloader和kernel,容器启动完成后会被卸载以节约内存资源
  • rootfs: 位于bootfs之上,表现为docker容器的根文件系统:
    • 传统模式中,系统启动之时,内核挂载rootfs时会首先将其挂载为“只读”模式,完整性自检完成后将其重新挂载为读写模式
    • docker中,rootfs由内核挂载为“只读”模式,而后通过"联合挂载“技术额外挂载一个”可写“层

Docker 镜像管理_第1张图片
启动一个busybox容器,命令ls查看容器内部,拥有完整意义上的文件系统:

$ docker container run --name shell -it busybox
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # exit
$ 

分层构建

分层构建的镜像:

  • 位于下层的镜像为父镜像(parent image),最底层的称为基础镜像(base image)
  • 最上层为"可读写”层,其下的均为"只读“层

Docker 镜像管理_第2张图片
如图,是一个Apache镜像。最底层是一个 Debian 的基础镜像,一个纯净的操作系统。在系统之上,添加了一个 emacs,这是一个代码编辑器。再然后添加了一个 Apache。这里每添加一个软件都是一个独立的层次。
最最下面的bootfs,在容器启动时,一旦引导完rootfs就会被卸载并移除(从内存中移除)。
对于一个容器,所有写操作,只能在最上层的可读写层进行。如果容器删除了,这个最上面的可读写层也会一起被删除。

操作系统基础镜像

关于Linux操作系统的基础镜像,可以参考下表来选择合适的基础镜像:

  • busybox: 临时测试用
  • alpine: 主要用于测试,也可用于生产环境
  • centos: 主要用于生产环境,支持CentOS/Red Hat,常用于追求稳定性的企业应用
  • ubuntu: 主要用于生产环境,常用于人工智能计算和企业应用
  • debian: 主要用于生产环境

推荐使用Alpine镜像,因为它被严格控制并保持最小尺寸(目前小于5MB),但它仍然是一个完整的发行版。
alpine的好处主要是小,并且基本功能全。用于测试是非常方便的,而且生产上也是可以用。虽然不建议这么做,主要是因为缺少调试工具。
busybox的镜像比alpine更小,它并不是一个系统发行版。最初这个工具是为了在一张软盘上创建一个可引导的 GNU/Linux 系统,这可以用作安装盘和急救盘。它是一个集成了三百多个最常用Linux命令和工具的软件。所以如果是需要启动一个容器并运行一些系统的工具和命令,那么可以使用这个作为基础镜像。
另外3个就是常用的Linux发行版,推荐在生产系统上用。镜像大也不是什么问题,因为容器是分层构建的,所以本地的多个镜像理论上是共用同一个基础镜像。

文件系统

Docker镜像的分层构建和联合挂载依赖于它的专有文件系统。
aufs
在早期这个文件系统是aufs(advanced multi-layered unification filesystem), 高级多层统一文件系统。
overlayfs
aufs的竞争产品是overlayfs,overlayfs在3.18版本开始被合并到Linux内核。使用docker info命令可以找到当前使用的文件系统:

Storage Driver: overlay2
 Backing Filesystem: xfs
 Supports d_tpe: true
 Native Overlay Diff: true

overlay2是一种抽象的二级文件系统,它需要建构在本地文件系统之上。上面的信息显示,这里作为基础的本地文件系统是xfs。
其他文件系统
docker的分层镜像,除了aufs,还支持btrfs,devicemapper和vfs等。早期默认支持的文件系统:

  • Ubuntu系统,默认使用aufs
  • CentOS系统,默认使用devicemapper

Device Mapper 是 Linux2.6 内核中支持逻辑卷管理的通用设备映射机制,它为实现用于存储资源管理的块设备驱动提供了一个高度模块化的内核构架。

Docker Registry

最著名的 Registry 就是Docker Hub: https://hub.docker.com/
其他的和有比如这个Quay: https://quay.io/

启动容器时,会先试图从本地获取相关的镜像。如果本地镜像不存在,再从Registry中下载镜像并保存到本地。
一个Registry通常由2部分组成:

  • Repository
  • Index

Repository(仓库)

Repository,由特定的docker镜像的所有迭代版本组成的镜像仓库。一个Registry中可以存在多个Repository。每个仓库可以包含多个Tag(标签),每个标签对应一个镜像。
Repository可分为顶层仓库用户仓库,用户仓库名称格式为“用户名/仓库名”。使用docker search命令看一下:

$ docker search --limit 3 nginx
NAME                  DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
nginx                 Official build of Nginx.                        11704               [OK]                
jwilder/nginx-proxy   Automated Nginx reverse proxy for docker con…   1628                                    [OK]
bitnami/nginx         Bitnami nginx Docker Image                      69                                      [OK]
$ 

这里显示了3个,第一个是没有用户名的属于顶层仓库。后面是用户仓库,可以看到分别属于的用户名。

Index

Idxex的作用:

  • 维护用户账户、镜像的校验以及公共命名空间的信息
  • 相当于为Registry提供了一个完成用户认证功能的检索接口

云原生

这里只是简单的提一下这个概念,主要是程序配置文件的问题。
镜像的使用有一个问题,就是镜像内部使用的配置信息。配置信息可以直接注入在镜像里,但是这样就要为不同的配置生成好多个不同版本的镜像。
云原生是一种为了云计算环境运行而生的应用程序,并且可以解决不同配置的信息的问题。
以Nginx为例,传统的开发运行在服务器上的程序,使用配置文件来管理配置。如果把它托管到容器云上运行,就会有诸多不便之处,最大的问题就是修改配置文件。
而那些云原生开发的程序,会使用对于云计算场景方便的接口来提供配置逻辑。具体到容器,相当于为应用程序加了一层外壳,再去操作里面的数据是不方便的。有一种做法是向容器传入环境变量来传递配置信息,而配置则可以从环境变量加载自动注入到配置中。
云原生的大量配置都可以直接通过环境变量来获取。

基于容器制作镜像

使用命令docker commint会把容器最上面的可写层,单独创建为一个镜像层,生成一个新的镜像。

其他制作镜像的方法,并且是制作镜像的最主要的方法是,基于Dockerfile制作镜像。这部分内容很重要也很多,需要单独再写一篇。

修改基础镜像的内容

基于busybox,添加一个httpd的服务。

$ docker run --name httpd -it busybox
/ # echo "

Hello world. Busybox httpd.

" > /var/www/index.html / # cat /var/www/index.html

Hello world. Busybox httpd.

/ #

在容器内部进行修改
现在创建好了一个html文件,但是下次docker再启动这个容器时这个文件是不会有的。现在需要做的是将之前做的改变保存好。

保存对容器的修改,生成新的镜像
要保持这个容器的运行状态,那就再另外开一个会话执行commit命令:

$ docker commit -p httpd
sha256:5bd093efd84001a2f7412292431ead5c760acef8f4e3a2298abf9f28aa7b3cd7
$ 

这里的-p参数是将容器处于暂停状态,这样可以防止镜像制作过程中可能会有操作来改变容器的内容,建议-p参数都加上。

修改镜像标签

查看镜像信息,新制作完成的镜像信息如下:

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
                            5bd093efd840        2 minutes ago       1.22MB
busybox             latest              e4db68de4ff2        4 weeks ago         1.22MB
$ 

由于制作的时候没有指明仓库名和标签名,所以都是空。这两个字段是允许为空的,这样只能通过镜像的ID来指明这个镜像。

添加标签信息
为了引用时方便,还是把仓库名和标签名加上吧:

$ docker image tag 5bd093efd840 myimg/httpd:v1
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg/httpd         v1                  5bd093efd840        9 minutes ago       1.22MB
busybox             latest              e4db68de4ff2        4 weeks ago         1.22MB
$ 

一个镜像可以有多个标签,再加一个latest标签:

$ docker image tag myimg/httpd:v1 myimg/httpd:latest
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg/httpd         latest              5bd093efd840        11 minutes ago      1.22MB
myimg/httpd         v1                  5bd093efd840        11 minutes ago      1.22MB
busybox             latest              e4db68de4ff2        4 weeks ago         1.22MB
$ 

这里可以确认下,2个标签的镜像的ID是一样的,所以这里是一个镜像,只是为这个镜像添加了2个标签。

删除标签
没有删除标签的命令,要删除某个标签,就直接用删除镜像的命令:

$ docker image tag myimg/httpd:v1 myimg/httpd:tmp1
$ docker image rm myimg/httpd:tmp1
Untagged: myimg/httpd:tmp1
$

这里又添加了一个标签,然后再把这个标签给删除,命令执行结果显示只是把指定的标签去掉了。所以同一个镜像打了多个标签,本地存的只有一份。删除某个标签的镜像,只是把这个标签从镜像标签的列表里去除。之后删除最后一个标签的时候才是真正的删除了一个镜像。

修改镜像默认启动命令

使用 inspect 命令可以查看docker对象的底层信息。这里要找的是镜像的底层信息中的默认启动的命令,具体如下:

$ docker image inspect busybox
            "Cmd": [
                "sh"
            ],
$

启动时运行的命令是sh,这个也是busybox镜像默认启动时运行的命令,因为制作新镜像的时候没有指定这个内容。
重新制作一版新的镜像,这次要指定默认启动时运行的命令:

$ docker commit -c 'CMD ["httpd", "-f", "-h", "/var/www/"]' -p httpd myimg/httpd:v2
sha256:850da6d87c65a2c6084cdbfcabbeeeaf6c13ddbb9fbb984fec5ca05cab38830d
$ 

参数-c不是用来指定命令的,而是指定所有要做的修改,当然这里只要修改启动的命令。
httpd命令参数说明
关于启动命令httpd -f -h /var/www/,这个具体可以去看httpd的参数说明。-f表示作为守护进程也就是在前台运行,而-h参数则是指定首页的路径。

启动验证镜像

带参数启动镜像:

$ docker container run --name httpd2 -d -p 8002:80 myimg/httpd:v2
80522bb422e16dae4ea052bcb36e51203f4d7b023fefdf3de4114598b3e95b29
$ 

镜像启动后,可以使用浏览器访问宿主机的IP地址加上映射的端口号来打开这个页面,比如:http://192.168.24.170:8002/

镜像导入和导出

可以在已有镜像的主机上把镜像打包,将打包的文件复制到另外一台主机上再把镜像导入,就可以在主机之间传递镜像了。这种方法不需要连接镜像仓库。

导出镜像

导出镜像就是将镜像导出到一个tar包:

$ docker image save -o httpd.tar myimg/httpd

这条命令省略了Tag标签,这样就会把整个仓库打包,就是打包所有的版本。
save命令仅有一个参数-o,就是指定导出的位置。如果没有-o参数,那就是输出到终端。不过也不能直接输出到终端,这样的做法是再通过输出重定向来把内容保存起来。所以这条命令的效果是一样的:

$ docker image save myimg/httpd > httpd2.tar

可以加上标签信息,就可以指定打包对应的Tag的镜像。镜像的参数可以传入多个,就打包多个镜像:

$ docker image save -o httpd3.tar myimg/httpd:v1 myimg/httpd:v2

导出为tar文件
导入的文件名可以任意指定,不过建议使用tar扩展名。这确实是一个tar包,使用tar命令来查看tar包内部的文件列表:

$ tar -tvf httpd.tar 
-rw-r--r-- 0/0            1491 2019-07-18 15:10 25079c1e47bf896a028e55d715dc06e251f3efe53ca655ad63f6085ce6a465a8.json
-rw-r--r-- 0/0            1464 2019-07-18 15:05 7f36d8e3488df22381081d68c7f2215750167250114abd0b2f31d99e81a7bfd7.json
drwxr-xr-x 0/0               0 2019-07-18 15:05 957ac2430f81aaa485efe07e872a460156d73e48f53f31a9743ed0d5f0fa44d7/
-rw-r--r-- 0/0               3 2019-07-18 15:05 957ac2430f81aaa485efe07e872a460156d73e48f53f31a9743ed0d5f0fa44d7/VERSION
-rw-r--r-- 0/0            1081 2019-07-18 15:05 957ac2430f81aaa485efe07e872a460156d73e48f53f31a9743ed0d5f0fa44d7/json
-rw-r--r-- 0/0            4608 2019-07-18 15:05 957ac2430f81aaa485efe07e872a460156d73e48f53f31a9743ed0d5f0fa44d7/layer.tar
drwxr-xr-x 0/0               0 2019-07-18 15:10 a24e93a2c2b0548055a10d18f0c88dc138c57ee6f13020538bf80da2bfefc59f/
-rw-r--r-- 0/0               3 2019-07-18 15:10 a24e93a2c2b0548055a10d18f0c88dc138c57ee6f13020538bf80da2bfefc59f/VERSION
-rw-r--r-- 0/0            1107 2019-07-18 15:10 a24e93a2c2b0548055a10d18f0c88dc138c57ee6f13020538bf80da2bfefc59f/json
-rw-r--r-- 0/0            4608 2019-07-18 15:10 a24e93a2c2b0548055a10d18f0c88dc138c57ee6f13020538bf80da2bfefc59f/layer.tar
drwxr-xr-x 0/0               0 2019-07-18 15:05 dea411b43d1b59da62f22c37c8507e7757c2dd9a5467a523f92e612d88e83ae8/
-rw-r--r-- 0/0               3 2019-07-18 15:05 dea411b43d1b59da62f22c37c8507e7757c2dd9a5467a523f92e612d88e83ae8/VERSION
-rw-r--r-- 0/0             406 2019-07-18 15:05 dea411b43d1b59da62f22c37c8507e7757c2dd9a5467a523f92e612d88e83ae8/json
-rw-r--r-- 0/0         1441280 2019-07-18 15:05 dea411b43d1b59da62f22c37c8507e7757c2dd9a5467a523f92e612d88e83ae8/layer.tar
-rw-r--r-- 0/0             579 1970-01-01 08:00 manifest.json
-rw-r--r-- 0/0             238 1970-01-01 08:00 repositories
$ 

从IMAGE ID可以看出,这里确实是将2个版本的镜像到打包了。

导出并压缩
用下面的方法完成导出并压缩:

$ docker save myimage:latest | gzip > myimage_latest.tar.gz

导入镜像

使用load命令可以方便的将镜像导入:

$ docker image load -i httpd3.tar 
6194458b07fc: Loading layer [==================================================>]  1.441MB/1.441MB
dd0dd7cb79c9: Loading layer [==================================================>]  4.608kB/4.608kB
Loaded image: myimg/httpd:latest
Loaded image: myimg/httpd:v1
698704828883: Loading layer [==================================================>]  4.608kB/4.608kB
Loaded image: myimg/httpd:v2
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myimg/httpd         v2                  25079c1e47bf        30 minutes ago      1.22MB
myimg/httpd         latest              7f36d8e3488d        35 minutes ago      1.22MB
myimg/httpd         v1                  7f36d8e3488d        35 minutes ago      1.22MB
$ 

上面最后一次打包的文件是 httpd3.tar。执行打包命令的时候指定了v1和v2标签,并没有指定latest标签。不过这里能看到所有的3个标签。所以标签只是一个标签,一个镜像可以有多个标签,但是不同标签的镜像是同一个镜像。

不使用-i参数的话,默认从标准输出导入,用下面的方法也是一样的:

$ docker image load < httpd3.tar 

导入压缩文件

Load an image or repository from a tar archive (even if compressed with gzip, bzip2, or xz) from a file or STDIN. It restores both images and tags.

导入镜像可以从tar文件,也可以从几种压缩文件直接导入。操作起来都一样,程序会自己识别。应该是通过文件名后缀吧。