Docker 初探

一、容器(Container)

简单来说,容器是对应用程序及其依赖库的一种封装

乍看上去,容器就像一个轻量级的虚拟机系统(VM),也封装了一个操作系统实例用来运行某些应用程序。
相比于传统的虚拟机,容器的优势主要在以下几个方面:

  • 容器与宿主机共享资源,使得其效率有了很大的提升。与主机中运行的应用程序相比,在容器中运行的应用程序几乎没有任何额外的开销。

  • 容器可移植的特性可以解决由于运行环境的微小变化引发的一系列问题。

  • 容器的轻量级特性意味着开发人员可以同时运行数十个容器,从而可以模拟出生产级别的分布式系统

  • 对于不使用云端应用的用户和开发者,用户可以节省下大量的安装和配置时间,也不用担心系统的依赖冲突等问题。而开发者同时也可以避免由于用户系统环境的差异导致的可用性问题。

总的来说,虚拟机的基本目标,是完整地虚拟出一个外部(独立)的系统环境,而容器是为了达到应用程序的可移植和自成一体。

而 Docker Engine 为运行容器提供了快捷方便的交互接口。

二、Docker 安装(Linux)

Linux 系统上的 Docker 安装,可以直接使用官方提供的安装脚本(https://get.docker.com),命令如下:
$ sudo wget -qO- https://get.docker.com/ | sh

$ sudo curl -sSL https://get.docker.com/ | sh

Mac OS 和 Windows 系统上的 Docker 安装,可参考官方文档 Docker for MacDocker for Windows

Docker 官方镜像访问缓慢,可以使用阿里云提供的加速服务(参考 镜像加速器

Docker 需要特定的权限才能运行,即普通用户执行 docker 命令时需要加上 sudo 。

$ docker version
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        e68fc7a
 Built:             Tue Aug 21 17:24:51 2018
 OS/Arch:           linux/amd64
 Experimental:      false
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.38/version: dial unix /var/run/docker.sock: connect: permission denied

可以将本地用户添加到 docker 用户组中,后续使用 docker 命令时即无需加上 sudo 前缀。
$ sudo usermod -aG docker <username>

$ docker version
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        e68fc7a
 Built:             Tue Aug 21 17:24:51 2018
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.1-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       e68fc7a
  Built:            Tue Aug 21 17:23:15 2018
  OS/Arch:          linux/amd64
  Experimental:     false

三、操作入门

Hello World
$ docker run debian echo "Hello World"
Unable to find image 'debian:latest' locally
latest: Pulling from library/debian
05d1a5232b46: Pull complete
Digest: sha256:07fe888a6090482fc6e930c1282d1edf67998a39a09a0b339242fbfa2b602fff
Status: Downloaded newer image for debian:latest
Hello World

docker run 命令用于加载容器并执行某个命令。上述命令中的 debian 用于指定所使用的镜像的名字。
如本地磁盘上没有名为 debian 的镜像文件,则 Docker 会检查在线的 Docker Hub 并将最新版本的 Debian 镜像下载到本地。
之后将下载好的镜像转化成运行的容器,并在容器中执行指定的命令。
命令执行完毕后,输出的结果传送到标准输出,容器停止运行。

交互式 Shell
$ docker run -i -t debian /bin/bash
root@4af9c13b78d7:/# echo "Hello from Container"
Hello from Container
root@4af9c13b78d7:/# exit
exit

上面的命令会在容器中打开一个交互式 Shell (就像是 ssh 到了一个远程主机上)。
-i-t 选项表示打开一个已绑定了 tty 的交互式会话
当使用 exit 命令退出 bash 后,运行中的容器也会停止。

可以使用 docker start -i <Container_name> 命令回到交互式 Shell 中

列出 / 删除容器

docker ps 命令可以列出当前正在运行的容器及其相关信息,如容器ID、使用的镜像、执行的命令、创建时间、当前状态和名称等,加上 -a 选项则列出所有容器(包含已停止运行的容器)

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
$
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                     PORTS               NAMES
f74ed03352d3        debian              "/bin/bash"            9 minutes ago       Exited (0) 8 minutes ago                       mystifying_beaver
45e9e3f3847b        debian              "echo 'Hello World'"   9 minutes ago       Exited (0) 9 minutes ago                       quirky_mirzakhani

可以使用 docker rm <Container_name> 命令删除已停止运行的容器。

$ docker rm mystifying_beaver
mystifying_beaver
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                      PORTS               NAMES
45e9e3f3847b        debian              "echo 'Hello World'"   13 minutes ago      Exited (0) 13 minutes ago                       quirky_mirzakhani

docker rm -v $(docker ps -aq -f status=exited) 命令可以删除所有已停止运行的容器

手动创建镜像文件

从 Docker Hub 上拉取的镜像很多为初始的精简系统,运行容器后,可以给容器内的系统安装软件并提交更改,做成新的镜像供后期使用。

使用 docker run 命令运行容器并安装软件
$ docker run -it --name cowsay --hostname cowsay debian /bin/bash
root@cowsay:/# apt-get update
...
root@cowsay:/# apt-get install -y cowsay fortune
...
root@cowsay:/# /usr/games/fortune | /usr/games/cowsay
 ________________________
< Don't get to bragging. >
 ------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

其中 --name 选项用于指定容器的名称,--hostname 选项用于指定容器系统的主机名。

使用 docker commit 命令将容器转换成镜像文件。
$ docker commit cowsay test/cowsayimage
sha256:2dcb2f4d09f824120db19f79d3bfbdb85c24d4888732bcc7eaae79f707d80e32
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test/cowsayimage    latest              2dcb2f4d09f8        13 minutes ago      159MB
debian              latest              f2aae6ff5d89        3 weeks ago         101MB

docker images 命令用来查看已在本地的镜像文件及其信息。

使用生成的镜像文件
$ docker run test/cowsayimage /usr/games/fortune | /usr/games/cowsay
 _______________________________________
/ Today is the first day of the rest of \
\ the mess.                             /
 ---------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
使用 Dockerfile 创建镜像文件

Dockerfile 其实就是一个简单的文本文件,里面包含了创建 Docker 镜像的一系列步骤。相比于手动创建 Docker 镜像,使用 Dockerfile 自动地创建镜像文件,省去了大量重复的操作,同时也便于分享给其他人。

编辑 Dockerfile
$ mkdir cowsay && cd cowsay
$ vim Dockerfile

然后在新建的 Dockerfile 中填入以下内容:

FROM debian:wheezy
RUN apt-get update && apt-get install -y cowsay fortune

其中 FROM 用于指定构建时使用的初始镜像文件,RUN 用于指定在镜像中执行的 Shell 命令

构建镜像并测试

使用 docker build 命令创建 Docker 镜像:

$ docker build -t test/cowsay-dockerfile .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM debian:wheezy
wheezy: Pulling from library/debian
703d6f3fb41c: Pull complete
Digest: sha256:d00f167f8f2e70ecc2e0f5410a3cb74cd4ad720e33b9810da6a2dcfa81dccfc0
Status: Downloaded newer image for debian:wheezy
 ---> 94825a89630c
Step 2/2 : RUN apt-get update && apt-get install -y cowsay fortune
 ---> Running in efcf246bde0f
...
Setting up cowsay (3.03+dfsg1-4) ...
Removing intermediate container efcf246bde0f
 ---> 88e9a0f834cd
Successfully built 88e9a0f834cd
Successfully tagged test/cowsay-dockerfile:latest

测试刚构建好的 Docker 镜像:

$ docker run test/cowsay-dockerfile /usr/games/cowsay "Moo"
 _____
< Moo >
 -----
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
ENTRYPOINT

Dockerfile 中的 ENTRYPOINT 选项可以用来指定容器运行时自动执行的命令。如将 Dockerfile 改为如下内容并重新构建:

FROM debian:wheezy
RUN apt-get update && apt-get install -y cowsay fortune
ENTRYPOINT ["/usr/games/cowsay"]
$ docker build -t test/cowsay-dockerfile .
...
$ docker run test/cowsay-dockerfile "Moo"
 _____
< Moo >
 -----
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

运行上述容器时则不需要再指定命令(/usr/games/cowsay)而只输入命令的参数即可。

可以在当前目录下新建一个 entrypoint.sh 脚本:

#!/bin/bash
if [ $# -eq 0 ]; then
    /usr/games/fortune | /usr/games/cowsay
else
    /usr/games/cowsay "$@"
fi

上述内容为 Shell 脚本文件,不作详细解释。作用是当 docker run 没有为容器中执行的命令提供参数时,执行 fortune | cowsay ,如提供了参数,则执行 cowsay <参数> 命令。

将 entrypoint.sh 文件添加执行权限:chmod +x entrypoint.sh
将 Dockerfile 改为如下版本:

FROM debian:wheezy
RUN apt-get update && apt-get install -y cowsay fortune
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

其中 COPY 选项用来将本地主机上的文件复制到镜像的文件系统里(类似于 cp 命令)

最终效果如下:

$ docker build -t test/cowsay-dockerfile .
...
$ docker run test/cowsay-dockerfile
 _________________________________________
/ You don't become a failure until you're \
\ satisfied with being one.               /
 -----------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
$ docker run test/cowsay-dockerfile Hello Moo
 ___________
< Hello Moo >
 -----------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

四、基本使用

端口转发

假如容器中运行着一个 Web 服务器,需要外部世界可以访问。此时可以使用 docker 的 -p-P 选项,将本地主机的端口转发至容器内的端口。如:

$ docker run -d -p 8000:80 nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
...
Status: Downloaded newer image for nginx:latest
be1364f343ae7aff9d5a6f6040b5d6ca69363a4480f04548988dd798ab04ab7b
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
be1364f343ae        nginx               "nginx -g 'daemon of…"   8 seconds ago       Up 7 seconds        0.0.0.0:8000->80/tcp   quirky_mclean
$ curl localhost:8000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
$

其中 docker run -d -p 8000:80 nginx 命令用于在后台-d 选项)启动一个由官方 nginx 镜像创建的容器,并将本地主机的 8000 端口映射到容器的 80 端口(-p 8000:80),即容器中 nginx 服务运行的端口。
可以看到,当使用 curl 命令访问本地主机的 8000 端口时,等同于访问了容器的 80 端口,即容器中的 nginx 服务。

-P 选项可以自动选择空闲的端口进行转发,无需指定本地主机或容器的端口。可参考以下实例:

$ ID=$(docker run -d -P nginx)
$ docker port $ID 80
0.0.0.0:32768
$ curl localhost:32768
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
关联容器

容器关联可以允许同一个主机上的多个容器相互交换数据。当使用默认的网络模型时,这些关联的容器通过其“内部”网络传输数据,即关联容器间的相互交流不会暴露给本地主机。

可参考以下实例:

$ docker run --name myredis -d redis
be53967c42fd3292dfd59fd5d15e7025fa436816e46f6e7cbc3c47b06ddb0047
$ docker run --rm -it --link myredis:redis redis /bin/bash
root@b27aca7d8c54:/data# redis-cli -h redis -p 6379
redis:6379> ping
PONG
redis:6379>

其中 docker run --name myredis -d redis 命令用于在后台启动一个 redis 容器,并将其命名为 myredis 。同时返回该容器的 ID 到标准输出。
docker run --rm -it --link myredis:redis redis /bin/bash 命令用于启动另一个 redis 容器,并以交互的方式访问其 Shell (-it /bin/bash)。--rm 选项表示该容器退出后将自动删除。

容器关联的操作则由 --link myredis:redis 选项实现。表示将新容器关联至已存在的 "myredis" 容器,并为 "myredis" 容器设置别名为 "redis" ,即可以在当前的新容器中通过别名(redis)访问。

该选项会在新容器的 /etc/hosts 文件中添加一条主机名为 redis 的记录,并将其指向 "myredis" 容器的 IP 地址。所以在当前容器的 Shell 中使用 redis-cli 命令访问 "myredis" 中的服务时,可以无需指定其 IP 地址,直接使用主机名 "redis" 即可。(redis-cli -h redis -P 6379

存储卷

可以在使用 docker run 时通过 -v 选项指定存储卷:

$ docker run -it --name container-test -h CONTAINER -v /data debian /bin/bash
root@CONTAINER:/# cd /data ; touch test-file
root@CONTAINER:/data# ls
test-file
root@CONTAINER:/data# exit
exit

上面的命令会将容器中的 /data 目录变成一个存储卷,该目录下的任何文件都会被复制到卷中。

可以使用 docker inspect 命令查看该存储卷在本地主机中的位置:

$ docker inspect -f {{.Mounts}} container-test
[{volume 7ae1... /var/lib/docker/volumes/7ae1.../_data /data local  true }]

可以在存储卷对应于本地主机的目录(/var/lib/docker/volumes/7ae1.../_data)中创建文件,容器中对应目录(/data)下则会立即出现同样的文件。

$ sudo ls /var/lib/docker/volumes/7ae1.../_data
test-file
$ sudo touch /var/lib/docker/volumes/7ae1.../_data/test-file2
$ docker start -i container-test
root@CONTAINER:/# ls /data
test-file  test-file2
root@CONTAINER:/#

存储卷还可以通过 Dockerfile 中的 VOLUME 选项指定。如:VOLUME /data

共享数据

可以使用 -v HOST_DIR:CONTAINER_DIR 选项在本地主机和一(或多)个容器间共享数据。如:
$ docker run -v /home/starky/data:/data debian ls /data

该命令会将本地主机上的 /home/starky/data 目录挂载到容器中的 /data 目录下,所有已经存在于 /home/starky/data 目录下的文件在容器中也同样能够被访问。
但是原本存在于容器的 /data 目录下的文件则被隐藏。
如某些配置文件可以一直存放在本地主机上,并在需要时挂载到通用镜像构建的容器中。

可以使用 docker run 命令的 --volumes-from CONTAINER 选项在容器间共享数据。如:

$ docker run -it -h NEWCONTAINER --volumes-from container-test debian /bin/bash
root@NEWCONTAINER:/# ls /data
test-file  test-file2
root@NEWCONTAINER:/#

上面的命令创建了一个新的容器,且通过 --volumes-from 选项,该容器可以访问之前的 container-test 容器中的存储卷(/data)。

数据容器

数据容器是一种特殊的容器,其唯一目的是为了方便其他容器之间共享数据。如创建一个 PostgreSQL 的数据容器:
$ docker run --name dbdata postgres echo "Data-only container for postgres"
该命令用于从 postgres 镜像创建一个容器,并初始化镜像里定义的存储卷。

之后可以通过 --volumes-from 选项使用数据容器里的存储卷:
$ docker run -d --volumes-from dbdata --name db1 postgres

参考资料

Using Docker: Developing and Deploying Software with Containers

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容