一:容器中运行简单应用程序
1:hello world
使用docker可以在容器中运行应用程序,使用docker run命令即可。比如下面的命令:
$ docker run ubuntu /bin/echo 'Hello world' Hello world
docker run命令用于运行一个新的容器,ubuntu指定了该容器运行基于的镜像是Ubuntu操作系统镜像。指定镜像后,Docker首先在Docker主机本地寻找镜像,如果找不到则去公共镜像仓库:Docker Hub下载。
接下来,就是指定要在新容器中运行的命令:/bin/echo 'Hello world'。
运行该容器时,Docker创建一个新的Ubuntu操作系统环境,并且在其中执行/bin/echo命令,然后将命令结果输出到终端。
这种情况下,容器只有在运行命令时处于running状态,一旦命令执行完成,输出” Hello world”之后,该容器也就停止运行了。
2:交互式的容器
运行以下命令:
$ docker run -t -i ubuntu /bin/bash root@af8bae53bdd3:/#
还是以ubunt为基础镜像,这次运行的命令是/bin/bash,在容器中加载一个BASH shell。docker命令中多了两个选项:-t和-i,-t用于在容器中分配一个新的终端,-i使得用户可以与容器进行交互。
因此,容器加载后,就可以在容器中执行其他命令了:
root@af8bae53bdd3:/# pwd / root@af8bae53bdd3:/# ls bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var root@af8bae53bdd3:/# exit
可见,在容器中,当前处于根目录下,并且根目录的结构就是典型的Linux文件系统。
运行exit退出当前shell,Bash shell进程也就停止了,因此容器也就停止运行了。
3:后台运行容器
使用以下命令,可以在后台运行容器:
$ docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done" 1e5535038e285177d5214659a068137486f96ee5c2e85a4ac52dc83f2ebe4147
这里使用-d选项,将容器置于后台运行,使其成为daemon进程。
这里在容器中运行的程序是一个在/bin/sh中,每隔1s打印一次hello world的程序。
运行该命令后,返回的是一个容器ID,利用该容器ID,可以使用docker ps命令,查看当前处于运行状态的容器:
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1e5535038e28 ubuntu /bin/sh -c 'while tr 2 minutes ago Up 1 minute insane_babbage
可见,刚刚运行的容器处于UP状态,该命令还列出了刚刚容器的名字:insane_babbage,可以根据该名字,使用docker logs命令可以得到容器的标准输出。查看容器中的命令输出:
$ docker logs insane_babbage hello world hello world hello world . . .
使用docker stop命令,可以停止该容器:
$ docker stop insane_babbage insane_babbage
再次使用docker ps命令查看,发现已经没有容器运行了:
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
二:容器中运行实际应用
1:docker客户端
每次在shell中使用docker命令,实际上是启动了一个docker客户端,docker客户端连接docker服务器,由服务器执行相应的命令。可以使用docker version命令,查看当前主机上安装的docker客户单和docker服务器版本:
[hh_93_197 /]# docker version Client: Version: 1.10.3 API version: 1.22 Go version: go1.5.3 Git commit: 20f81dd Built: Thu Mar 10 15:39:25 2016 OS/Arch: linux/amd64 Server: Version: 1.10.3 API version: 1.22 Go version: go1.5.3 Git commit: 20f81dd Built: Thu Mar 10 15:39:25 2016 OS/Arch: linux/amd64
2:查看docker命令帮助信息
可以使用命令docker --help,查看docker帮助信息,也可以使用--help,查看docker子命令的帮助信息,比如:
[hh_93_197 /]# docker run --help Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] Run a command in a new container -a, --attach=[] Attach to STDIN, STDOUT or STDERR --add-host=[] Add a custom host-to-IP mapping (host:ip) --blkio-weight Block IO (relative weight), between 10 and 1000 --blkio-weight-device=[] Block IO weight (relative device weight) --cpu-shares CPU shares (relative weight) --cap-add=[] Add Linux capabilities --cap-drop=[] Drop Linux capabilities ...
3:在docker中运行web服务器
使用以下命令,可以在容器中运行一个python的web服务器:
$ docker run -d -P training/webapp python app.py
这里使用的镜像是training/webapp,其中包含了简单的Python flask web应用。-d选项使容器后台运行,-P选项将容器中的端口映射到主机上的端口,后续会详细解释。
4:查看容器
使用docker ps命令查看刚刚运行的容器:
[hh_93_197 /]# docker ps -l CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 026fcbe4b3dd training/webapp "python app.py" 23 hours ago Up 3 seconds 0.0.0.0:32770->5000/tcp stoic_davinci
docker ps命令,加上-l选项,可以列出最后一个启动的容器,docker ps命令只能列出运行状态的容器,通过-a选项,可以列出所有状态的容器。
这里,在PORTS列下的信息是;0.0.0.0:32770->5000/tcp,因为在docker run中指定了-P选项,因此docker会将容器内的端口映射到主机的端口上,这里就是将容器的tcp端口5000映射到主机的32770端口上。因此,访问主机的32770端口,实际访问的就是容器中的5000端口(默认的Python flask端口):
使用-p选项,也可以自行指定容器和主机之间的端口映射关系,比如下面的命令:
[hh_93_197 /]# docker run -d -p 80:5000 training/webapp python app.py
该命令就将容器的5000映射到主机上的80端口了:
使用docker port命令,可以查看容器的端口映射情况:
[hh_93_197 /]# docker port sad_archimedes 5000/tcp -> 0.0.0.0:80
使用docker logs命令查看容器的日志:
[hh_93_197 /]# docker logs -f sad_archimedes * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) 10.1.80.66 - - [16/Mar/2016 07:41:30] "GET / HTTP/1.1" 200 - 10.1.80.66 - - [16/Mar/2016 07:41:30] "GET /favicon.ico HTTP/1.1" 404 - 10.1.80.66 - - [16/Mar/2016 07:41:33] "GET / HTTP/1.1" 200 - ...
这里的-f选项,类似于tail -f中的选项。
使用docker top命令,可以查看容器中运行的进程:
[hh_93_197 /]# docker top sad_archimedes UID PID PPID C STIME TTY TIME CMD root 21864 24559 0 15:40 ? 00:00:00 python app.py
可见容器中只运行了python的web应用。
使用docker inspect命令,可以获得容器的更详细的信息,该命令返回一个JSON格式信息:
[hh_93_197 /]# docker inspect sad_archimedes [ { "Id": "a7910dd9cafff213db102d679de6a71b2051f980c6d46c3080870973f39af162", "Created": "2016-03-16T07:40:27.345500271Z", "Path": "python", "Args": [ "app.py" ], "State": { "Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 21864, "ExitCode": 0, "Error": "", "StartedAt": "2016-03-16T07:40:27.726836748Z", "FinishedAt": "0001-01-01T00:00:00Z" }, "Image": "sha256:6fae60ef344644649a39240b94d73b8ba9c67f898ede85cf8e947a887b3e6557", "ResolvConfPath": "/var/lib/docker/containers/a7910dd9cafff213db102d679de6a71b2051f980c6d46c3080870973f39af162/resolv.conf", "HostnamePath": "/var/lib/docker/containers/a7910dd9cafff213db102d679de6a71b2051f980c6d46c3080870973f39af162/hostname", "HostsPath": "/var/lib/docker/containers/a7910dd9cafff213db102d679de6a71b2051f980c6d46c3080870973f39af162/hosts", "LogPath": "/var/lib/docker/containers/a7910dd9cafff213db102d679de6a71b2051f980c6d46c3080870973f39af162/a7910dd9cafff213db102d679de6a71b2051f980c6d46c3080870973f39af162-json.log", "Name": "/sad_archimedes", "RestartCount": 0, "Driver": "devicemapper", "MountLabel": "", "ProcessLabel": "", "AppArmorProfile": "", "ExecIDs": null, "HostConfig": { "Binds": null, "ContainerIDFile": "", "LogConfig": { "Type": "json-file", "Config": {} }, "NetworkMode": "default", "PortBindings": { "5000/tcp": [ { "HostIp": "", "HostPort": "80" } ] }, "RestartPolicy": { "Name": "no", "MaximumRetryCount": 0 }, "VolumeDriver": "", "VolumesFrom": null, "CapAdd": null, "CapDrop": null, "Dns": [], "DnsOptions": [], "DnsSearch": [], "ExtraHosts": null, "GroupAdd": null, "IpcMode": "", "Links": null, "OomScoreAdj": 0, "PidMode": "", "Privileged": false, "PublishAllPorts": false, "ReadonlyRootfs": false, "SecurityOpt": null, "UTSMode": "", "ShmSize": 67108864, "ConsoleSize": [ 0, 0 ], "Isolation": "", "CpuShares": 0, "CgroupParent": "", "BlkioWeight": 0, "BlkioWeightDevice": null, "BlkioDeviceReadBps": null, "BlkioDeviceWriteBps": null, "BlkioDeviceReadIOps": null, "BlkioDeviceWriteIOps": null, "CpuPeriod": 0, "CpuQuota": 0, "CpusetCpus": "", "CpusetMems": "", "Devices": [], "KernelMemory": 0, "Memory": 0, "MemoryReservation": 0, "MemorySwap": 0, "MemorySwappiness": -1, "OomKillDisable": false, "PidsLimit": 0, "Ulimits": null }, "GraphDriver": { "Name": "devicemapper", "Data": { "DeviceId": "66", "DeviceName": "docker-8:6-538261252-268466c24d896a48119782e8aaf98fc2acf786ce8db86a806b0bddace665169d", "DeviceSize": "10737418240" } }, "Mounts": [], "Config": { "Hostname": "a7910dd9caff", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "5000/tcp": {} }, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "python", "app.py" ], "Image": "training/webapp", "Volumes": null, "WorkingDir": "/opt/webapp", "Entrypoint": null, "OnBuild": null, "Labels": {}, "StopSignal": "SIGTERM" }, "NetworkSettings": { "Bridge": "", "SandboxID": "b41b21041836349d572454db6822fdd8b1128762ec83a8d2fcad7c31de93fcfc", "HairpinMode": false, "LinkLocalIPv6Address": "", "LinkLocalIPv6PrefixLen": 0, "Ports": { "5000/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "80" } ] }, "SandboxKey": "/var/run/docker/netns/b41b21041836", "SecondaryIPAddresses": null, "SecondaryIPv6Addresses": null, "EndpointID": "ef7147d741b0a2da2dc768fd23bd9433436a52bedf5773132cbefb6457fe0077", "Gateway": "172.17.0.1", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "MacAddress": "02:42:ac:11:00:02", "Networks": { "bridge": { "IPAMConfig": null, "Links": null, "Aliases": null, "NetworkID": "4948e842e58f3fedd25afe97bf292d53d7023ddeb90fefb7825103b56c5251b8", "EndpointID": "ef7147d741b0a2da2dc768fd23bd9433436a52bedf5773132cbefb6457fe0077", "Gateway": "172.17.0.1", "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:11:00:02" } } } } ]
还可以返回特定的信息:
[hh_93_197 /]# docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' sad_archimedes 172.17.0.2
5:容器的导入和导出
可以使用docker export命令,将容器导出到本地文件:
[hh_93_197 ~]# docker export fa0fecad952f > ubuntu.tar
查看ubuntu.tar的内容:
可以使用docker import命令,从容器快照文件中再导入为镜像:
[hh_93_197 ~]# cat ubuntu.tar | docker import - test/ubuntu:v1.0 sha256:50288f29d8ad89d09f0a341201a9118118b7d7314466b2c14cea9184734a1056 [hh_93_197 ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE test/ubuntu v1.0 50288f29d8ad 9 seconds ago 187.7 MB
6:停止,启动、删除容器
使用docker stop命令可以停止容器,使用docker start命令可以启动容器,使用docker rm命令可以删除容器,注意删除之前必须先停止容器。
三:镜像
docker中,镜像是容器的基础。每次运行docker run命令时,必须指定要使用的镜像。运行容器时,docker首先在docker主机上查找镜像,找不到则去镜像仓库Docker Hub Registry中寻找。
1:列出主机上的镜像
使用docker images命令可以列出主机上本地镜像:
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu 14.04 1d073211c498 3 days ago 187.9 MB busybox latest 2c5ac3f849df 5 days ago 1.113 MB training/webapp latest 54bb4e8718e8 5 months ago 348.7 MB
一般镜像库保存着一个镜像的多个版本,比如ubuntu镜像就有10.04, 12.04, 12.10, 13.04, 13.10和14.04版本。这些版本通过tag来区分,比如:ubuntu:14.04。所以,docker命令中指定镜像tag的例子如下:
$ docker run -t -i ubuntu:14.04 /bin/bash
如果不指定tag,则docker使用最新的镜像:ubuntu:latest。
2:下载新的镜像
使用docker pull命令可以下载镜像,比如:
$ docker pull centos Pulling repository centos b7de3133ff98: Pulling dependent layers 5cc9e91966f7: Pulling fs layer 511136ea3c5a: Download complete ef52fb1fe610: Download complete . . . Status: Downloaded newer image for centos
3:查找镜像
可以在Dcoker Hub(https://hub.docker.com/)上查找镜像,也可以使用命令docker search来查找,比如想找所有安装了Sinatra的镜像,可以:
$ docker search sinatra NAME DESCRIPTION STARS OFFICIAL AUTOMATED training/sinatra Sinatra training image 0 [OK] marceldegraaf/sinatra Sinatra test app 0 mattwarren/docker-sinatra-demo 0 [OK] luisbebop/docker-sinatra-hello-world 0 [OK] bmorearty/handson-sinatra handson-ruby + Sinatra for Hands on with D... 0 subwiz/sinatra 0 bmorearty/sinatra 0 . . .
注意,Official Repositories指的是由Docker公司维护的镜像,Automated Builds 指的是允许验证源和内容的镜像。
像ubuntu这种形式的镜像,称为基或者根镜像,它们是由docker公司构建、验证和支持的。像training/sinatra这样的镜像,是从属于某个docker社区成员的用户镜像,由该Docker社区成员构建并维护,这些镜像总是以创建镜像的用户名为前缀,比如这里的training。
想要下载training/sinatra镜像,可以这样:$docker pull training/sinatra
4:创建自己的镜像
可以通过以下两种方式升级和创建镜像。
a:可以在某个镜像基础上,创建并提交新的镜像;
b:可以通过Dockerfile来指定创建镜像的命令;
a:升级并提交一个镜像
要升级一个镜像,首选需要创建一个基于该镜像的容器:
[hh_93_197 /]# docker run -t -i ubuntu /bin/bash root@8ff95f120ff2:/#
比如这里在/tmp目录下创建一个readme.txt,当做升级镜像的动作:
root@0843755bad78:/# cd /tmp root@0843755bad78:/tmp# cat readme.txt this is my image
然后直接运行exit命令退出,这样容器就停止了。此时,可以根据该容器,使用docker commit命令创建一个新的镜像:
[hh_93_197 /]# docker commit -m "Added readme.txt" -a "hh" 0843755bad78 hh/ubuntu:v1 sha256:554a3b147ad10933c7643c65241f02b87d55bf4342f14a852aefe7ff04344a2e
这里使用了-m和-a选项。-m选项后跟一个注释信息(类似于git的-m命令),-a命令用于指定作者。接下来就是指定根据哪个容器创建镜像了,这里0843755bad78就是该容器的ID,然后就是新镜像的名称和tag:hh/ubuntu:v1,其中包含了新镜像的用户名hh,镜像的名称ubuntu,以及镜像的tag :v1.
使用docker images命令查看镜像:
[hh_93_197 /]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE hh/ubuntu v1 554a3b147ad1 3 minutes ago 187.9 MB docker-whale latest 1b1c047367b4 47 hours ago 247 MB ubuntu latest 07c86167cdc4 12 days ago 187.9 MB hello-world latest 690ed74de00f 5 months ago 960 B docker/whalesay latest 6b362a9f73eb 9 months ago 247 MB training/webapp latest 6fae60ef3446 10 months ago 348.7 MB
使用新的镜像创建一个容器:
[hh_93_197 /]# docker run -i -t hh/ubuntu:v1 /bin/bash root@88400cbc3e23:/# cat /tmp/readme.txt this is my image
可见,该容器中确实是有/tmp/readme.txt文件的。
b:根据Dockerfile创建镜像
使用docker commit创建镜像有些麻烦且不易于分享。可以使用docker build命令,根据Dockerfile创建镜像。
首先需要创建一个Dockerfile文件,该文件包含了构建镜像的一系列指令:
[hh_93_197 ~]# mkdir ubuntu_hh [hh_93_197 ~]# cd ubuntu_hh/ [hh_93_197 ~/ubuntu_hh]# touch Dockerfile
在Dockerfile文件中,每条指令都创建了镜像的一个新的层(镜像是分层的),简单的例子如下:
# This is a comment FROM ubuntu:latest MAINTAINER hh RUN touch /tmp/readme.txt RUN echo "this is my ubuntu V2" >> /tmp/readme.txt
Dockerfile中,以”#”开头的是注释语句,每条指令以一个大写的声明开始。第一条FROM指令用于告诉docker新建的镜像以哪个镜像为基础,这里以ubuntu:latest为基础创建新镜像。MAINTAINER用于指定镜像的作者。
之后是两个RUN指令,每条RUN指令用于在镜像中运行命令,这里首先是新建文件/tmp/readme.txt,然后将"this is my ubuntu V2"写入该文件。
现在,根据该Dockerfile,使用docker build命令创建新镜像:
[hh_93_197 ~/ubuntu_hh]# docker build -t hh/ubuntu:v2 . Sending build context to Docker daemon 2.048 kB Step 1 : FROM ubuntu:latest ---> 07c86167cdc4 Step 2 : MAINTAINER hh ---> Running in 249d7e34ce6b ---> b77f4b33c9ab Removing intermediate container 249d7e34ce6b Step 3 : RUN touch /tmp/readme.txt ---> Running in 23c8fdeeca89 ---> 447a799e19f4 Removing intermediate container 23c8fdeeca89 Step 4 : RUN echo "this is my ubuntu V2" >> /tmp/readme.txt ---> Running in 758d510038a3 ---> 450ca66f56da Removing intermediate container 758d510038a3 Successfully built 450ca66f56da
使用-t选项,指定新镜像的作者为hh,镜像名称为ubuntu,tag为v2。而且指定当前目录作为Dockerfile文件所在的目录,当然也可以指定Dockerfile所在目录的绝对路径。
运行该命令之后,docker首先upload构建环境:基本上就是构建目录的内容;接下来就是执行Dockerfile文件中每一条指令,可以看见每条指令都创建一个新的临时容器,然后在容器中运行指令(比如:---> Running in 23c8fdeeca89),然后提交容器的变化(跟docker commit类似),生成一个临时镜像(比如:---> b77f4b33c9ab),然后删除临时容器(Removing intermediate container 249d7e34ce6b)。
最后,当所有指令执行完成之后,新的镜像就构建成功了,这里的镜像ID是450ca66f56da。所有的临时镜像也都被删除了,使用docker images查看新建的镜像:
[hh_93_197 ~/ubuntu_hh]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE hh/ubuntu v2 450ca66f56da 36 seconds ago 187.9 MB hh/ubuntu v1 554a3b147ad1 27 minutes ago 187.9 MB docker-whale latest 1b1c047367b4 47 hours ago 247 MB ubuntu latest 07c86167cdc4 12 days ago 187.9 MB hello-world latest 690ed74de00f 5 months ago 960 B docker/whalesay latest 6b362a9f73eb 9 months ago 247 MB training/webapp latest 6fae60ef3446 10 months ago 348.7 MB
可以根据新建的镜像运行一个容器:
[hh_93_197 ~/ubuntu_hh]# docker run -t -i hh/ubuntu:v2 /bin/bash root@b6f9f8bbe491:/# cat /tmp/readme.txt this is my ubuntu V2
5:更改镜像的tag
可以使用docker tag命令更改一个镜像的tag,比如:
docker tag 450ca66f56da ouruser/sinatra:devel
使用docker images查看镜像,发现同一个镜像ID具有多个标签:
[hh_93_197 ~/ubuntu_hh]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE hh/ubuntu v2 450ca66f56da 28 minutes ago 187.9 MB ouruser/sinatra devel 450ca66f56da 28 minutes ago 187.9 MB ...
6:镜像摘要
使用V2或者更新格式的镜像,具有一个叫做摘要的可寻址内容的标识符。只要用于创建镜像的输入不变,则镜像的摘要值就是可预期的,列出镜像的摘要值,可以在docker images中使用--digests选项:
$ docker images --digests | head REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE ouruser/sinatra latest sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf 5db5f8471261 11 hours ago 446.7 MB
当从2.0的镜像库push或者pull时,push或pull命令的输出包含摘要信息,可以在pull时使用摘要信息:
$ docker pull ouruser/sinatra@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
也可以在create、run和rmi命令中,以及Dockerfile中的FROM指令中,使用摘要指定镜像。
7:镜像的导入和导出
使用docker save命令,可以导出镜像到本地文件:
[hh_93_197 ~]# docker save -o ubuntu.tar ubuntu
查看ubuntu.tar的内容:
使用docker load命令,可以将本地文件导入到本地镜像库,比如:
[hh_93_197 ~]# docker load --input ubuntu.tar
或者
[hh_93_197 ~]# docker load < ubuntu.tar
注意:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
8:其他
可以使用docker push命令,将新建的镜像上传至Docker Hub。
可以使用docker rmi命令删除本地的镜像。删除镜像前,需要确认没有基于该镜像的容器运行。
https://docs.docker.com/engine/userguide/containers/dockerizing/
https://docs.docker.com/engine/userguide/containers/usingdocker/
https://docs.docker.com/engine/userguide/containers/dockerimages/