内容简介:【编者的话】容器在软件开发、测试和部署环节应用的越来越广泛,那么测试人员应该如何掌握容器技术呢?应该掌握哪些基本的容器操作呢?本文通过容器化一个 Python Web 应用,来快速掌握 Docker 容器和镜像的基本操作。容器技术中两个基本的概念是容器和镜像。可以通过一个类比来理解,容器就是进程,镜像就是程序。程序运行起来就是进程,镜像运行起来就是容器。程序要想能运行起来,除了有我们自己编写的业务代码还要有依赖,还要借助于操作系统,把代码、依赖和操作系统打包在一起就是镜像,镜像中包含程序运行起来的所有要素,
【编者的话】容器在软件开发、测试和部署环节应用的越来越广泛,那么测试人员应该如何掌握容器技术呢?应该掌握哪些基本的容器操作呢?本文通过容器化一个 Python Web 应用,来快速掌握 Docker 容器和镜像的基本操作。
容器技术中两个基本的概念是容器和镜像。可以通过一个类比来理解,容器就是进程,镜像就是程序。程序运行起来就是进程,镜像运行起来就是容器。
程序要想能运行起来,除了有我们自己编写的业务代码还要有依赖,还要借助于操作系统,把代码、依赖和操作系统打包在一起就是镜像,镜像中包含程序运行起来的所有要素,因此镜像可以“Build Once,Run Anywhere”,能够保证一致性。这是容器技术带给我们的非常大的益处。
容器是镜像的动态表现,本质是一个的进程,镜像启动成为进程时,Docker引擎借助Linux Namespace 技术修改了应用进程看待操作系统的“视图”,只能“看到”某些指定的内容,并自以为自己是PID=1的1号进程。Docker引擎还利用Linux Cgroups技术对容器进程能够使用的系统资源,比如CPU、内存等进行了限制。因此,容器就是被Docker引擎加了很多限制的进程。
本文不详细介绍容器和镜像底层原理的更多内容,将聚焦在软件测试工作中常用的对容器和镜像的基础操作。
要想执行本文里面的Docker命令,前提是有一台安装了Docker的MacOS或者 Linux 操作系统的机器。安装方法请参考: https://www.docker.com/get-started
构建一个镜像
一个完整镜像通常包含应用本身和操作系统,当然还包含需要的依赖软件。
首先准备一个应用。新建一个本文文件,起名叫app.py,写入下面的内容,实现一个简单的Web应用:
from flask import Flask import socket import os app = Flask(__name__) @app.route('/') def hello(): html = "<h3>Hello {name}!</h3>" \ "<b>主机名:</b> {hostname}<br/>" return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname()) if __name__ == "__main__": app.run(host='0.0.0.0', port=8082)
在这段代码中,使用Flask框架启动了一个Web服务器,而它唯一的功能是:如果当前环境中有“NAME”这个环境变量,就把它打印在“Hello”后,否则就打印“Hello world”,最后再打印出当前环境的 hostname。
这个应用的依赖文件requirements.txt存在于与app.py同级目录中,内容是:
$ cat requirements.txt Flask
将这样一个应用在容器中跑起来,需要制作一个容器镜像。Docker使用Dockerfile文件来描述镜像的构建过程。在本文中,Dockerfile内容定义如下:
# FROM指令指定了基础镜像是python:3.6-alpine,这个基础镜像包含了Alpine Linux操作系统和 Python 3.6 FROM python:3.6-alpine # WORKDIR指令将工作目录切换为/app WORKDIR /app # ADD指令将当前目录下的所有内容(app.py、requirements.txt)复制到镜像的 /app 目录下 ADD . /app # RUN指令运行pip命令安装依赖 RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt # EXPOSE指令暴露允许被外界访问的8083端口 EXPOSE 8083 # ENV指令设置环境变量NAME ENV NAME World # CMD指令设置容器内进程为:python app.py,即:这个 Python 应用的启动命令 CMD ["python", "app.py"]
这个Dockerfile中用到了很多指令,把包括FROM、WORKDIR、ADD、RUN、EXPOSE、ENV和CMD。指令的具体含义已经以注释的方式写在了Dockerfile中,大家可以查看。通常我们构建镜像时都会依赖一个基础镜像,基础镜像中包含了一些基础信息,我们依赖基础构建出来的新镜像将包含基础镜像中的内容。
需要再详细介绍一下CMD指令。CMD指定了python app.py为这个容器启动后执行的进程。CMD [“python”, “app.py”] 等价于在容器中执行 “python app.py”。
另外,在使用 Dockerfile 时,还有一种 ENTRYPOINT 指令。它和 CMD 都是 Docker 容器进程启动所必需的参数,完整执行格式是:“ENTRYPOINT CMD”。
默认情况下,Docker 会为你提供一个隐含的 ENTRYPOINT,即:/bin/sh -c。所以,在不指定 ENTRYPOINT 时,比如在我们这个例子里,实际上运行在容器里的完整进程是:/bin/sh -c “python app.py”,即 CMD 的内容就是 ENTRYPOINT 的参数。正是基于这样的原理,Docker 容器的启动进程为实际为 ENTRYPOINT,而不是 CMD。
需要注意的是,Dockerfile 里的指令并不都是只在容器内部的操作。就比如 ADD,它指的是把当前目录(即 Dockerfile 所在的目录)里的文件,复制到指定容器内的目录当中。
更多能在Dockerfile中使用的指令,可以参考官方文档: https://docs.docker.com/engine ... rence 。
根据前面的描述,现在我们的整个应用的目录结构应该如下这样:
$ ls Dockerfile app.py requirements.txt
执行下面的指令可以构建镜像:
$ docker build -f /path/to/Dockerfile -t helloworld . Sending build context to Docker daemon 4.608kB Step 1/7 : FROM python:3.6-alpine ---> 5e7f84829665 Step 2/7 : WORKDIR /app ---> Using cache ---> dbb4a00a8f68 Step 3/7 : ADD . /app ---> fd33ac91c6c7 Step 4/7 : RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt ---> Running in 6b82e863d802 Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Collecting Flask Downloading https://pypi.tuna.tsinghua.edu.cn/packages/f2/28/2a03252dfb9ebf377f40fba6a7841b47083260bf8bd8e737b0c6952df83f/Flask-1.1.2-py2.py3-none-any.whl (94 kB) Collecting click>=5.1 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/dd/c0/4d8f43a9b16e289f36478422031b8a63b54b6ac3b1ba605d602f10dd54d6/click-7.1.1-py2.py3-none-any.whl (82 kB) Collecting Jinja2>=2.10.1 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/27/24/4f35961e5c669e96f6559760042a55b9bcfcdb82b9bdb3c8753dbe042e35/Jinja2-2.11.1-py2.py3-none-any.whl (126 kB) Collecting itsdangerous>=0.24 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/76/ae/44b03b253d6fade317f32c24d100b3b35c2239807046a4c953c7b89fa49e/itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB) Collecting Werkzeug>=0.15 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/cc/94/5f7079a0e00bd6863ef8f1da638721e9da21e5bacee597595b318f71d62e/Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB) Collecting MarkupSafe>=0.23 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz (19 kB) Building wheels for collected packages: MarkupSafe Building wheel for MarkupSafe (setup.py): started Building wheel for MarkupSafe (setup.py): finished with status 'done' Created wheel for MarkupSafe: filename=MarkupSafe-1.1.1-py3-none-any.whl size=12629 sha256=1f965945354a52423078c573deb1a8116965e67b2467c3640264d7f02058b06d Stored in directory: /root/.cache/pip/wheels/06/e7/1e/6e3a2c1ef63240ab6ae2761b5c012b5a4d38e448725566eb3d Successfully built MarkupSafe Installing collected packages: click, MarkupSafe, Jinja2, itsdangerous, Werkzeug, Flask Successfully installed Flask-1.1.2 Jinja2-2.11.1 MarkupSafe-1.1.1 Werkzeug-1.0.1 click-7.1.1 itsdangerous-1.1.0 Removing intermediate container 6b82e863d802 ---> d672a00c1a2f Step 5/7 : EXPOSE 8083 ---> Running in b9b2338da3f3 Removing intermediate container b9b2338da3f3 ---> e91da5a22e20 Step 6/7 : ENV NAME World ---> Running in d7e5d19f3eed Removing intermediate container d7e5d19f3eed ---> 4f959f34d486 Step 7/7 : CMD ["python", "app.py"] ---> Running in 99a97bedace0 Removing intermediate container 99a97bedace0 ---> 3bc3e537ebb7 Successfully built 3bc3e537ebb7 Successfully tagged helloworld:latest
其中,-t 的作用是给这个镜像加一个 Tag,即:起一个好听的名字。docker build 会自动加载当前目录下的 Dockerfile 文件,然后按照顺序执行Dockerfile文件中的指令。
上面的命令执行完成后,就生成了一个镜像。可以通过下面的指令查看:
$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE helloworld latest 3bc3e537ebb7 2 minutes ago 103MB
还可以通过docker inspect helloworld:latest查看镜像的元信息:
$ docker inspect helloworld:latest [ { "Id": "sha256:3bc3e537ebb79d26c6fdbcf841499f23d0a9c7726ad1f533f585fe677f8a9c6b", "RepoTags": [ "helloworld:latest" ], "RepoDigests": [], "Parent": "sha256:4f959f34d486fe8c6127fb65609937dbac4923e56f652090e469d51264b5c4e0", "Comment": "", "Created": "2020-04-13T14:43:15.6562968Z", "Container": "99a97bedace054b2a3eee01eced0294e25602f3b53ffa8a39cce00209d051fc0", "ContainerConfig": { "Hostname": "99a97bedace0", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "8083/tcp": {} }, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "LANG=C.UTF-8", "GPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D", "PYTHON_VERSION=3.6.10", "PYTHON_PIP_VERSION=20.0.2", "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/d59197a3c169cef378a22428a3fa99d33e080a5d/get-pip.py", "PYTHON_GET_PIP_SHA256=421ac1d44c0cf9730a088e337867d974b91bdce4ea2636099275071878cc189e", "NAME=World" ], "Cmd": [ "/bin/sh", "-c", "#(nop) ", "CMD [\"python\" \"app.py\"]" ], "Image": "sha256:4f959f34d486fe8c6127fb65609937dbac4923e56f652090e469d51264b5c4e0", "Volumes": null, "WorkingDir": "/app", "Entrypoint": null, "OnBuild": null, "Labels": {} }, "DockerVersion": "19.03.8", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "8083/tcp": {} }, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "LANG=C.UTF-8", "GPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D", "PYTHON_VERSION=3.6.10", "PYTHON_PIP_VERSION=20.0.2", "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/d59197a3c169cef378a22428a3fa99d33e080a5d/get-pip.py", "PYTHON_GET_PIP_SHA256=421ac1d44c0cf9730a088e337867d974b91bdce4ea2636099275071878cc189e", "NAME=World" ], "Cmd": [ "python", "app.py" ], "Image": "sha256:4f959f34d486fe8c6127fb65609937dbac4923e56f652090e469d51264b5c4e0", "Volumes": null, "WorkingDir": "/app", "Entrypoint": null, "OnBuild": null, "Labels": null }, "Architecture": "amd64", "Os": "linux", "Size": 103263332, "VirtualSize": 103263332, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/c349c378637d8211bb08eab95d5e7abdbf6d394c304ba57a64b8664a5c728b2a/diff:/var/lib/docker/overlay2/c042b9e207d25ca167ae375d7a312941f7f88ce6b441ced9eb0cc76556746c8f/diff:/var/lib/docker/overlay2/22bc7eaff7b47078258b461bb65430e13960c3350db7b54191b2174de5ff2dad/diff:/var/lib/docker/overlay2/fc429777fd588295c0e2c495ed3ebdabca23dc62d75b0265e7a4b2a324c33622/diff:/var/lib/docker/overlay2/9e497ccfb39b20ee332dc7c4b2f68de724e6a605a593af1852dc1512602ac35a/diff:/var/lib/docker/overlay2/4453a778a9bf6e17ceee3861a4183e9dc7a5e2a50d2d9fecf4e2cd4c2b042286/diff:/var/lib/docker/overlay2/520410b2e383a10d8c3b2e8d8f47a4e35c290691af2dc99c0fe75666b7eb2dcd/diff", "MergedDir": "/var/lib/docker/overlay2/559dbcb8413a066faa40522b411cf4d8712ba680cf89cb6a4e41577a961e5c25/merged", "UpperDir": "/var/lib/docker/overlay2/559dbcb8413a066faa40522b411cf4d8712ba680cf89cb6a4e41577a961e5c25/diff", "WorkDir": "/var/lib/docker/overlay2/559dbcb8413a066faa40522b411cf4d8712ba680cf89cb6a4e41577a961e5c25/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", "sha256:d87eb7d6daff38d5b2dd47afce11b28cda4fb41fd1401f1c154437663ca51145", "sha256:00891a9058ec5ca0a3420a0307f4cdfaf6b58b8f1ec05d63e527e12fe3c69351", "sha256:9a8b7b2b0c33880049913fb325184f127d74f363102a5ac9bff26f0f0d749e9a", "sha256:a9a7f132e4de0299fa104c819e0accb4f2566137ee17f7f53cd8f2c67103e9e4", "sha256:46c42cfd4d054eec8c7452c41bbf78abba12a6feddcbf7832b47301c4ee5d413", "sha256:1af4857074cc9bd9a060613386068bcfc2ca06fae0df3690d840328070c9f4a0", "sha256:fc7b1fecdbe2f45d44d04b33017a2f89d2ac3928d2fb75dfb3db12738416b91f" ] }, "Metadata": { "LastTagTime": "2020-04-13T14:43:15.6852866Z" } } ]
元信息中包含了镜像的全部信息,包括镜像的tag,构建时间,环境变量等。
如果镜像不再需要了,可以通过docker image rm删除镜像。
$ docker image rm -f b054a66ef574 $ docker image rm b054a66ef574
运行镜像
有了镜像,就可以通过下面的指令来运行镜像得到容器了。
$ docker run -p 8082:8082 helloworld * Serving Flask app "app" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:8082/ (Press CTRL+C to quit)
上面命令中,镜像名helloworld后面,什么都不用写,因为在Dockerfile中已经指定了CMD。否则,我就得把进程的启动命令加在后面:
$ docker run -p 8082:8082 helloworld python app.py
从现在看,容器已经正确启动,我们使用curl命令通过宿主机的IP和端口号,来访问容器中的web应用。
$ curl http://0.0.0.0:8082/ <h3>Hello World!</h3><b>主机名:</b> 59b607239c3a<br/>
不过这里返回的主机名有点怪怪的,其实这个59b607239c3a就是容器的ID,可以通过运行docker ps指令查看运行中的容器。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 59b607239c3a helloworld "python app.py" 3 seconds ago Up 2 seconds 0.0.0.0:8082->8082/tcp, 8083/tcp flasky
从输出中可以看到容器的ID,容器是基于哪个镜像的启动的,容器中的进程,容器的启动时间及端口映射情况,以及容器的名字。
使用docker inspect 59b607239c3a命令,可以查看容器的元数据,内容非常丰富。
分享镜像
大家一定用过代码分享平台GitHub,在Docker世界中分享镜像的平台是Docker Hub,它“学名”叫镜像仓库(Repository)。任何人都可以从上面拉取镜像或者Push自己的镜像上去。
为了能够上传镜像,首先需要注册一个 Docker Hub 账号,然后使用docker login命令登录:
$ 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: liuchunming Password: Login Succeeded
在push到Docker Hub之前,需要先给镜像指定一个版本号:
$ docker tag helloworld liuchunming/helloworld:v1
liuchunming是我在Docker Hub 上的账户名。v1是我给这个镜像起的版本号。接着执行下面的指令就可以镜像push到Docker Hub上了:
$ docker push liuchunming/helloworld:v1
一旦提交到Docker Hub上,其他人就可以通过docker pull liuchunming/helloworld:v1将镜像下载下来了。
在企业内部,也可以搭建一个跟Docker Hub类似的镜像存储系统。感兴趣的话,可以查看VMware的Harbor项目。
镜像加速
鉴于国内网络问题,从 https://hub.docker.com/ 拉取Docker镜像十分缓慢,我们可以需要配置加速器来解决。在Mac电脑任务栏,点击Docker Desktop应用图标 -> Perferences。在settings页面中进入Docker Engine修改和添加Docker daemon 配置文件即可。
修改完成之后,点击Apply & Restart按钮,Docker就会重启并应用配置的镜像地址了。之后在拉取镜像时,将会快很多。
进入容器中玩玩
运行Web服务的容器,通常是以后台进程启动的。就是在docker run指令后面加上-d选项。比如以后台方式运行上面的Web容器:
$ docker run -d -p 8082:8082 --name flasky2 helloworld cc733dd4310d40a10fe8093411abb002dfe18e7737e58c047910a4836424f746
如果想进入到一个正在运行的容器中做一些操作,可以通过docker exec指令:
$ docker exec -it flasky2 /bin/sh /app #
-it选项指的是连接到容器后,启动一个terminal(终端)并开启input(输入)功能。-it后面接的是容器的名称,/bin/sh表示进入到容器后执行的命令。还可以通过容器的ID进入容器中,容器的ID可以通过docker ps命令查看。
docker exec的实现原理,其实是利用了容器的三大核心技术之一的Namespace。一个进程可以选择加入到某个进程(运行中的容器)已有的 Namespace 当中,从而达到“进入”这个进程所在容器的目的。更细节的原理这里不在细究。
进入到容器中,就可以在终端上进行一些操作了,比如在容器中新建一个readme.md文件:
/app # ps PID USER TIME COMMAND 1 root 0:00 python app.py 24 root 0:00 /bin/sh 29 root 0:00 ps /app# touch readme.md /app# exit
这个readme.md文件只会在这个容器中存在,用镜像启动的其他容器中不会有这个文件。
我们还可以将正在运行的容器,commit成新的镜像。
$ docker commit flasky2 liuchunming033/helloworld:v2
还有一种进入容器的方法是使用docker attach container_id,不过这种方法不建议使用,因为它有个明显的缺点:当多个窗口同时attach到同一个容器时,所有的窗口都会同步的显示,假如其中的一个窗口发生阻塞时,其它的窗口也会阻塞。
当试图进入一个已经停止的容器中时,则会提示你Container is not running:
$ docker exec -it flasky2 /bin/sh Error response from daemon: Container cc733dd4310d40a10fe8093411abb002dfe18e7737e58c047910a4836424f746 is not running
与宿主机共享文件
容器技术使用了Rootfs机制和Mount Namespace构建出了一个同宿主机完全隔离开的文件系统环境。但是我们使用过程中经常会遇到这样两个问题:
- 容器里进程新建的文件,怎么才能让宿主机获取到?
- 宿主机上的文件和目录,怎么才能让容器里的进程访问到?
这正是Docker Volume要解决的问题:Volume机制,允许你将宿主机上指定的目录,挂载到容器里面进行读取和修改。通过-v选项,可以宿主机目录~/work挂载进容器的 /test 目录当中:
$ docker run -d -p 8082:8082 -v ~/work:/test --name flasky helloworld 574c252649cb3ef1824ce8b6151b2ce87b4512ba1bac08d0735b1676905e3161
这样,在容器flasky中 会创建/test目录,在/test目录下创建的文件,在宿主机的目录~/work中可看到。在宿主机的目录~/work中创建的文件,在容器flasky中/test目录下也可以看到。
执行docker inspect CONTAINER_ID命令,命令输出的Mounts字段中Source的值就是宿主机上的目录,Destination是对应的容器中的目录:
"Mounts": [ { "Type": "bind", "Source": "/Users/chunming.liu/work", "Destination": "/test", "Mode": "", "RW": true, "Propagation": "rprivate" } ],
强烈建议如上所示指明挂载宿主机的哪个目录。如果不显示声明宿主机目录,那么 Docker 就会在宿主机上创建一个临时目录 /var/lib/docker/volumes/[VOLUME_ID]/_data,然后把它挂载到容器的 /test 目录上。
想要查看宿主机临时目录的内容,需要先查看到VOLUME_ID,可以通过下面方式查看:
$ docker volume ls DRIVER VOLUME NAME local 24c7e73e88b23bdb198e190d9c3227201827735b1b92872c951f755847ff88ee
接着,如果是在MacOS电脑上,则执行下面两个命令:
$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty $ ls /var/lib/docker/volumes/24c7e73e88b23bdb198e190d9c322720182 7735b1b92872c951f755847ff88ee/_data/
如果是Linux电脑上,则不需要执行screen那个命令。
下面,实验一下在容器的/test目录下添加一个文件 text.txt 是否在宿主机中可以访问到,首先进入容器创建文件:
$ docker exec -it flasky /bin/sh $ cd test/ $ touch text.txt
回到宿主机,就会发现 text.txt 已经出现在了宿主机上对应的临时目录里了:
$ ls /var/lib/docker/volumes/24c7e73e88b23bdb198e190d9c322720182 7735b1b92872c951f755847ff88ee/_data/ text.txt
将容器的目录映射到宿主机的某个目录,一个重要使用场景是持久化容器中产生的文件,比如应用的日志,方便在容器外部访问。强烈建议在
给容器加上资源限制
其实容器是运行在宿主机上的特殊进程,多个容器之间是共享宿主机的操作系统内核的。默认情况下,容器并没有被设定使用操作系统资源的上限。
有些情况下,我们需要限制容器启动后占用的宿主机操作系统的资源。Docker可以利用Linux Cgroups机制可以给容器设置资源使用限制。
Linux Cgroups 的全称是 Linux Control Group。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。Docker正是利用这个特性限制容器使用宿主上的CPU、内存。
下面启动容器的方式,给这个Python应用加上CPU和Memory限制:
$ docker run -it --cpu-period=100000 --cpu-quota=20000 -m 300M helloworld
–cpu-period和–cpu-quota组合使用来限制容器使用的CPU时间。表示在–cpu-period的一段时间内,容器只能被分配到总量为 --cpu-quota 的 CPU 时间。-m选项则限制了容器使用宿主机内存的上限。
上面启动容器的命令,将容器使用的CPU限制设定在最高20%,内存使用最多是300MB。
重启、停止与删除
使用过docker ps查看当前运行中的容器,如果加上-a选项,则可以查看运行中和已经停止的所有容器。现在,看一下我的系统中目前的所有容器:
$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 525a8c3fc769 helloworld "python app.py" 4 hours ago Up 3 minutes 80/tcp hardcore_feistel 1695ed10e2cb helloworld "python app.py" 4 hours ago Up 3 minutes 0.0.0.0:5000->80/tcp focused_margulis7 a242ecaf6cf6 helloworld "python app.py" 5 hours ago Exited (0) 4 hours ago dazzling_khayyam be0439b30b2a helloworld "python app.py" 5 hours ago Created vigilant_laland
从输出中可以看到目前有四个容器,有两个容器处于Up状态,也就是处于运行中的状态,一个容器处于Exited(0)状态,也就是退出状态,一个处于Created状态。
docker ps -a的输出结果,一共包含7列数据,分别是CONTAINER ID、IMAGE、COMMAND、CREATED、STATUS、PORTS和NAMES。这些列的含义分别如下所示:
- CONTAINER ID:容器ID,唯一标识容器
- IMAGE:创建容器时所用的镜像
- COMMAND:在容器最后运行的命令
- CREATED:容器创建的时间
- STATUS:容器的状态
- PORTS:对外开放的端口号
- NAMES:容器名(具有唯一性,Docker负责命名)
获取到容器的ID之后,可以对容器的状态进行修改,比如容器1695ed10e2cb进行停止、启动、重启:
$ docker stop flasky $ docker start flasky $ docker restart flasky
删除容器,有两种操作:
$ docker rm flasky $ docker rm -f flasky
不带-f选项,只能删除处于非Up状态的容器,带上-f则可以删除处于任何状态下的容器。
容器可以先创建容器,稍后再启动。也就是可以先执行docker create创建容器(处于Created状态),再通过docker start以后台方式启动容器。docker run命令实际上是docker create和docker start的组合。
维持容器运行状态
docker run指令有一个参数--restart,在容器中启动的进程正常退出或发生OOM时, docker会根据--restart的策略判断是否需要重启容器。但如果容器是因为执行docker stop或docker kill退出,则不会自动重启。
docker支持如下restart策略:
- no – 容器退出时不要自动重启。这个是默认值。
- on-failure[:max-retries] – 只在容器以非0状态码退出时重启。可选的,可以退出docker daemon尝试重启容器的次数。
- always – 不管退出状态码是什么始终重启容器。当指定always时,docker daemon将无限次数地重启容器。容器也会在daemon启动时尝试重启容器,不管容器当时的状态如何。
- unless-stopped – 不管退出状态码是什么始终重启容器。不过当daemon启动时,如果容器之前已经为停止状态,不启动它。
在每次重启容器之前,不断地增加重启延迟(上一次重启的双倍延迟,从100毫秒开始),来防止影响服务器。这意味着daemon将等待100ms,然后200ms,400ms,800ms,1600ms等等,直到超过on-failure限制,或执行docker stop或docker rm -f。如果容器重启成功(容器启动后并运行至少10秒),然后delay重置为默认的100ms。
下面是两种重启策略:
$ docker run --restart=always flasky # restart策略为always,使得容器退出时,Docker将重启它。并且是无限制次数重启。 $ docker run --restart=on-failure:10 flasky #restart策略为on-failure,最大重启次数为10的次。容器以非0状态连续退出超过10次,Docker将中断尝试重启这个容器。
可以通过docker inspect来查看已经尝试重启容器了多少次。例如,获取容器flasky的重启次数:
$ docker inspect -f "{{ .RestartCount }}" flasky
或者获取上一次容器重启时间:
$ docker inspect -f "{{ .State.StartedAt }}" 1695ed10e2cb
总结
本篇文章以容器化Python Web应用为案例,讲解了Docker容器使用的主要场景。包括构建镜像、启动镜像、分享镜像、在镜像中操作、在镜像中挂载宿主机目录、对容器使用的资源进行限制、管理容器的状态和如何保持容器始终运行。熟悉了这些操作,也就基本上摸清了Docker容器的核心功能,在软件测试过程中遇到使用容器的场景,也就基本能搞定了。
参考资料:
原文链接:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 通过 Kubernetes 和容器实现 DevOps
- 通过暴露的docker.sock文件接管容器
- 通过暴露的Docker API渗透容器:AESDDoS僵尸网络恶意软件分析
- 通过消除对特权容器的需求来提高 Istio Deployment 的安全性
- 容器技术之容器镜像篇
- 通过实例入门Golang
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JSON 在线解析
在线 JSON 格式化工具
RGB CMYK 转换工具
RGB CMYK 互转工具