Docker 是一个能让程序跑在一个它 没法感知的、用于 隔绝外界环境里的容器的工具。
最初是 dotCloud 公司创始人 Solomon Hykes 发起的一个公司内部项目,并于 2013 年 3 月以 Apache 2.0 受权协议开源,代码主要在 GitHub 上进行维护。Docker 项目后来还加入了 Linux 基金会,并成立推进 开放容器联盟(OCI)。html
Docker 使用 Google 推出的 Go 语言开发实现,基于 Linux 内核的 cgroup,namespace,以及 UnionFS 等技术。最初实现基于 LXC,从 0.7 版本后去除 LXC,转而开始使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。node
在 2017 年 4 月 21 日 Pull Request #32691 将原有的 Docker 项目改名为 Moby,由 Moby 构建出 Docker CE(社区版),而新的 Docker 项目则构建出 Docker EE(企业版本)。mysql
上图是 Docker Doc 关于 Docker 和传统虚拟机区别的截图。linux
Docker 利用了 Linux 内核的 cgroup 和 namespace 为程序的执行创造一个隔离的环境,使得程序感知不到外界的存在,其自己仍然是跑在原有的内核上的;而虚拟机则是经过 Hypervisor 模拟了一整套系统环境,虚拟机里的程序是跑在虚拟机内核上的。因为虚拟机须要模拟一整套操做系统环境,所以开销比 Docker 容器要高不少不少。nginx
你能够把跑在容器里的程序想象成楚门(楚门的世界男主),他并不知道本身生活在一个精心布置的超大影棚里,可是他仍然是活在现实世界里的,呼吸着现实世界中的空气,吃着和咱们差很少的食物;跑在虚拟机里的程序就好像活在动画片里的小猪佩奇,他的一切都是虚拟的,虽然小猪佩奇并不知道本身活在动画片里,可是很显然的是它和咱们彻底不在一个世界(不是同一个系统内核)。git
特性 | 容器 | 虚拟机 |
---|---|---|
启动 | 秒级 | 分钟级 |
硬盘使用 | 通常为 MB | 通常为 GB |
性能 | 接近原生 | 弱于 |
系统支持量 | 单机上千个容器 | 通常几十个 |
镜像 是一个包含操做系统完整 root 文件系统 的、只读的,由多层文件系统联合而成的打包文件。
Docker 为了让应用无感知的跑在容器中,提供了一套完整的 root 文件系统,好比官方镜像 library/ubuntu 就包含了一整套 root 文件系统。像 apache、nginx 都是基于该镜像构建的,因为 library/ubuntu 自己很大,因此 Docker 采用了分层存储的方式。github
本文伪装你已经安装了 Docker,上图经过 docker pull nginx
从 官方 Registry(下面会提到这是啥)拉取 nginx 镜像,拉取 nginx 至关于 library/nginx:latest,library 表示 nginx 是官方镜像,所以能够省略,:latest 表示拉取标签为 latest 的镜像。拉取后能够看到存在两个镜像,由于 nginx 镜像自己就是基于 library:ubuntu:16.04 镜像的。sql
上图经过 docker pull httpd
拉取了 apache 镜像,因为 ubuntu:16.04 镜像已经在本地存在了,所以拉取的时候不会重复拉取。从而节约拉取时间。这就是 Docker 分层存储的意义。docker
镜像的只读能够理解成之前的光盘 CD,是不可更改的。为了模拟实现对光盘 CD 的写的功能,会创建两层文件系统,一层是光盘 CD 的只读文件系统;另一层是存放更改数据的可写的文件系统。从而实现模拟更改镜像的做用。Docker 也是采用这种相似的分层的方式。shell
如图,能够看出 ubuntu:15.04 是由不少层文件系统(镜像)堆叠造成的,最底层是 root 文件系统(d3a1f33e8a5a)。这几层文件系统都被设置成只读的。多层文件系统利用了上面提到的 UnionFS、AUFS、OverlayFS,这是一类文件系统,这种联合挂载文件系统最先就是用于解决 CD 这种只读文件系统的修改问题,Docker 以前使用 AUFS,可是因为 AUFS 不被 linus 喜欢(被 linus 评价为稠密、不可读,无注释)致使 AUFS 一直没有被合并到 Linux 的主分支中。Docker 在 1.12 之后已经将默认的文件系统从 AUFS 替换成 OverlayFS2。由于 OverlayFS2 已经被合并进了 Linux 的主干分支中。
上面咱们拉取了 nginx 镜像到本地,咱们可使用 docker container start nginx
(省略了 latest 标签)来运行这个镜像。运行以前会先建立一个容器(其实本质就是建立了一层可读写的文件系统,以提供程序运行时的读写支持),而后就会启动程序,让程序跑在一个隔离环境(不是虚拟环境)里。你还能够经过 docker container commit>
来对当前层进行提交(就好像 Git 提交同样),从而造成一个新的镜像,可是这种方式是不推荐的;这是由于在程序运行过程当中可能会产生一些垃圾文件,而若是这些垃圾文件被提交后,新的镜像又是不可修改的,只会增大镜像的体积。具体怎么建立镜像会在下面说到。
能够看到上图中在建立容器的时候其实就是建立了一个容器可读写层。你还能够经过 docker container stop<container ID>
中止容器的运行,至关于 kill 掉容器内的正在运行的程序,可是建立容器时建立的可读写的文件系统依然存在。因此你依然能够经过 docker start <container ID>
来重启程序。
镜像构建完成后,能够很容易的在宿主机器上运行,可是若是其余机器要使用这个镜像,咱们就须要一个集中存储、分发镜像的服务,Docker Registry 就是这样的服务。一个 Docker Registry 能够包含多个仓库,每一个仓库能够包含多个标签,每一个标签对应一个镜像。
就拿上面的 library/nginx:latest 举例,library 表示这个镜像是官方镜像,若是不是官方镜像,这里通常填注册在 Docker Registry 的用户名;library/nginx 是仓库的名字,latest 是该仓库一个标签。
诚然,官方的 Docker Registry 是世界上最大的镜像分发服务,官方还提供了 Docker Registry 镜像 用于搭建私有镜像分发服务。并且 DockerHub 和社区一块儿制做了大量的、高质量的镜像,使得咱们构建镜像更为方便。
前面提到能够经过 docker commit
生成新的镜像,可是这种方式并不推荐(缘由已经说明),因此咱们通常仍是采用 Dockerfile 的方式。下面的实践以 github-issue-rss 为例,demonstate how to containerization a normal project。
首先建立一个 Dockerfile 文件,内容以下:
FROM node:9-alpine MAINTAINER mrcode "mrcodehang@outlook.com" WORKDIR /src # 表示容器内的程序运行时的当前目录 COPY . /src # 把构建 Dockerfile 文件目录下的文件所有复制到镜像的 /src 目录下 RUN npm install -g yarn && yarn install # 构建时执行 EXPOSE 3000 # 暴露容器的 3000 端口到外面 ENTRYPOINT ["npm", "start"] # 执行 docker start <container ID> 时就会执行 npm start
Dockerfile 里的每一行开头的大写字母单词叫作 Dockerfile 指令。每执行一条指令就会增长一层镜像(本质是执行了一次 docker commit
,而 AUFS 最大的层数是 127 层,所以 Dockerfile 里的层数最好不要太多!
FROM 表示基于哪个镜像构建,node:9-alpine
表示基于官方的 node 镜像构建,标签 9-alpine
表示这是一个 node 9 的镜像,同时该 node9 镜像是基于 alpine 镜像构建的,alpine 是 Linux 的一个精简发行版,大小只有 5MB 左右,而 Ubuntu 镜像大小接近 200MB。
RUN 指令会在构建镜像时执行,使用 && 符号是为了减小 RUN 命令的使用次数,减小最终镜像的层数。
EXPOSE 指令让外界能经过容器的 3000 端口进行网络通讯。
ENTRYPOINT 表示执行 docker start <container ID>
时就会执行 npm start(启动程序);还能够写成 ENTRYPOINT npm start
这种形式;而后就能够开始构建了。有的同窗喜欢在 npm start 后加上 '&',来让容器默认后台运行;但这只会致使容器没法启动,由于容器自己的执行彻底是依靠程序自己的进程的,当程序自己进程没有挂载在 docker 容器上时,容器就会直接结束,容器结束后容器内的进程也被杀掉。因此要知道保持容器运行的正是容器内的进程自己!
图中执行命令最后有一个 '.',这是将当前目录做为上下文传递给 Docker daemon;Docker 的工做方式是基于 C-S 架构的,你须要将构建的所在目录传给 docker daemon,这也是上面的 Dockerfile 文件的 COPY 指令的当前目录。
接下来建立容器,一个镜像能够建立多个容器(其实就是建立多个在同一层的读写层)。
docker run
会拉取远程的镜像(若是本地没有的话),接着它会建立一个容器,基于 mrcode/github-issue-rss:test 镜像(只有 latest 标签能够省略);-v 会建立一个数据卷(volume),表示当容器对 /var/log/github-issue-rss/ 写入数据时至关于写在了宿主机的 ~/github-issue-rss/log
目录上,从而维持容器的无状态特性(无状态特性是指容器在运行时尽可能不要将重要数据存储在容器所在的读写层里,虽然那是一层读写层,可是是用来存放程序运行时产生的临时文件的,不该将重要数据放在里面);-d 表示 daemon 执行程序,不然的话容器进程会挂载在当前 shell 上,通常经过 -d 挂载到 docker daemon 进程上;—rm 表示容器退出后自动删除容器,这是推荐的用法,也是容器的无状态特性的体现。
容器进程具备和容器内程序自己进程相同的生命周期,容器进程用来启动容器内程序,至关于 Linux 内的 init 进程;当容器内程序被
docker stop <container ID>
杀掉时,容器就会退出,留下一个已建立的读写层文件系统,这也是容器存在的标志。
因为建立容器仅仅是建立了一个可读写的文件系统,因此容器的存在是很是很是轻量级的。即使对一个镜像建立多个容器,镜像自己是不会被从新拷贝的,而是最大程度的复用,这是由于镜像内的多层文件系统的每一层都被设置成只读的。
你能够经过 docker container ls
查看当前正在运行的全部容器,若是还想查看已退出的容器,加上一个 -a
参数。使用 docker container start/stop
能够启动/关闭容器。
最后能够经过 docker push mrcode/github-issue-rss:test
发布到 DockerHub 上,分享到社区。
github-issue-rss
is a tool converts the issues on GitHub to RSS.
这个工具须要用到了 mysql,为了之后方便数据迁移,我决定使用 mysql 镜像,mysql 镜像能够把全部状态存放在宿主机的一个文件夹下。那我如今不只须要启动 mysql 和 github-issue-rss 镜像,还须要创建他们之间的网络链接关系,事情变得麻烦了。有一个工具叫 docker-compose (本文伪装你已经安装了这个工具)能够把这一切自动化。下面是项目根目录的一个 docker-compose.yml 文件:
version: "3" services: db: image: mysql:5.7 volumes: - ~/.github-issue-rss/mysql:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: rootroot MYSQL_DATABASE: rss MYSQL_USER: mrcode MYSQL_PASSWORD: github-issue-rss github-issue-rss: image: mrcode/github-issue-rss:v0.1.0 depends_on: - db ports: - "3000:3000" restart: always environment: MYSQL_PORT: 3306 MYSQL_HOST: db MYSQL_SCHEMA: rss MYSQL_USERNAME: mrcode MYSQL_PASSWORD: github-issue-rss LOG_FILE: /var/log/github-issue-rss/ volumes: - ~/.github-issue-rss/log/:/var/log/github-issue-rss/
在 docker-compose 的世界里没有容器,只有服务。它认为它启动了两服务 db 和 github-issue-rss。没有哪一个是主服务,全部服务都是平等的。
在 db service 中,设置了 volumes,将 mysql 的数据存储在 ~/.github-issue-rss/mysql/ 里,还能够设置更多的 volume。restart 表示只要服务执行失败就重启,防止依赖的 service 尚未启动完成时致使的错误引起连锁反应。给两个 service 配置的 environment 来创建二者的数据链接,github-issue-rss 代码会读取这个环境变量,而后链接到 db 服务,能够看到 github-issue-rss 里的环境变量 MYSQL_HOST 设置为 db,这是由于 docker-compose 会在启动的服务配置里创建这个 DNS 映射关系。
还能够经过 docker-compose down
来中止而且删除服务对应的容器。
如今你只须要克隆仓库到本地,而后执行 docker-compose up 就能够启动 github-issue-rss 了,由于 github-issue-rss 镜像自己已经构建并发布到 Docker Hub 了。
使用 Docker 能够经过定制应用镜像来实现持续集成、持续交付、部署。开发人员经过 Dockerfile 进行镜像构建,结合持续集成系统进行集成测试,而运维人员则能够在生产环境中快速部署该镜像。甚至结合持续部署进行自动部署。
并且使用 Dockerfile 使镜像的构建透明化,不只能够帮助开发人员理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。
Docker 和微服务架构简直就是浑然天成,站在 Docker 的角度,软件本质是容器的组合:业务逻辑容器、数据库容器、存储容器、队列容器……Docker 使得软件拆分红若干的标准化容器,而后像积木同样的搭建起来。这正是微服务的思想:软件把任务外包出去,让各类外部服务完成这些任务,软件自己只是底层服务的调用中心和组装层。