docker
Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。
Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。
Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。
总体来说,Docker 的接口相当简单,用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样
Docker使用场景:
- web应用的自动化打包和发布;
- 自动化测试和持续集成、发布;
- 在服务型环境中部署和调整数据库或其他的后台应用;
- 从头编译或者扩展现有的OpenShift或Cloud Foundry平台来搭建自己的PaaS环境。
docker构成:
Docker系统有两个程序:docker服务端和docker客户端。
- 其中docker服务端是一个服务进程,管理着所有的容器。
- docker客户端则扮演着docker服务端的远程控制器,可以用来控制docker的服务端进程。
- 大部分情况下,docker服务端和客户端运行在一台机器上。
Docker 的用途
Docker 的主要用途,目前有三大类。
(1)提供一次性的环境。比如,本地测试他人的软件、持续集成的时候提供单元测试和构建的环境。
(2)提供弹性的云服务。因为 Docker 容器可以随开随关,很适合动态扩容和缩容。
(3)组建微服务架构。通过多个容器,一台机器可以跑多个服务,因此在本机就可以模拟出微服务架构。
Docker 的安装
Docker 是一个开源的商业产品,有两个版本:
- 社区版(Community Edition,缩写为 CE));
- 企业版(Enterprise Edition,缩写为 EE)
企业版包含了一些收费服务,个人开发者一般用不到。故目前只学习社区版。mac安装docker
安装成功后,需要验证一下:
$ docker version
$ docker info
//上面两个方法都可以验证
- 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:21:31 2018
OS/Arch: darwin/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:29:02 2018
OS/Arch: linux/amd64
Experimental: true
- docker info 打印结果
Containers: 8
Running: 2
Paused: 0
Stopped: 6
Images: 5
Server Version: 18.06.1-ce
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 468a545b9edcd5932818eb9de8e72413e616e86e
runc version: 69663f0bd4b60df09991c08812a60108003fa340
init version: fec3683
Security Options:
seccomp
Profile: default
Kernel Version: 4.9.93-linuxkit-aufs
Operating System: Docker for Mac
OSType: linux
Architecture: x86_64
CPUs: 2
Total Memory: 1.952GiB
Name: linuxkit-025000000001
ID: 4J63:BVUQ:VXQ7:PT5T:KJEQ:HTKK:VNK2:V6NC:YEUH:C4QK:IYSW:VJWP
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): true
File Descriptors: 24
Goroutines: 51
System Time: 2018-08-31T01:36:05.427042926Z
EventsListeners: 2
HTTP Proxy: gateway.docker.internal:3128
HTTPS Proxy: gateway.docker.internal:3129
Registry: https://index.docker.io/v1/
Labels:
Experimental: true
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false
或者可以指定查询特定项
$ docker --version
Docker version 18.03, build c97c6d6
$ docker-compose --version
docker-compose version 1.22.0, build 8dd22a9
$ docker-machine --version
docker-machine version 0.14.0, build 9ba6da9
hello world 实例
在确定了docker的版本之后,就可以尝试打印一下hello-world:
$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/engine/userguide/
image文件
Docker 把应用程序及其依赖,打包在 image 文件里面。只有通过这个文件,才能生成 Docker 容器。image 文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。
image 是二进制文件。实际开发中,一个 image 文件往往通过继承另一个 image 文件,加上一些个性化设置而生成。
举例来说,你可以在 Ubuntu 的 image 基础上,往里面加入 Apache 服务器,形成你的 image。
image 文件是通用的,一台机器的 image 文件拷贝到另一台机器,照样可以使用。一般来说,为了节省时间,我们应该尽量使用别人制作好的 image 文件,而不是自己制作。即使要定制,也应该基于别人的 image 文件进行加工,而不是从零开始制作。
为了方便共享,image 文件制作完成后,可以上传到网上的仓库。Docker 的官方仓库 Docker Hub 是最重要、最常用的 image 仓库。此外,出售自己制作的 image 文件也是可以的。
- 在打印hello-world成功之后,可以操作生成的image文件:
# 列出本机的所有 image 文件。
$ docker image ls
# 删除 image 文件
$ docker image rm [imageName]
- 列出本机所有image文件
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 71c43202b8ac 34 hours ago 109MB
docker latest 8f769f924e65 8 days ago 153MB
ubuntu latest 16508e5c265d 8 days ago 84.1MB
hello-world latest 2cb0d9787c4d 7 weeks ago 1.85kB
learn/tutorial latest a7876479f1aa 5 years ago 128MB
容器文件
image 文件生成的容器实例,本身也是一个文件,称为容器文件。也就是说,一旦容器生成,就会同时存在两个文件: image 文件和容器文件。而且关闭容器并不会删除容器文件,只是容器停止运行而已。
- 查询和删除容器文件
# 列出本机正在运行的容器
$ docker container ls
# 列出本机所有容器,包括终止运行的容器
$ docker container ls --all
// 在输入run后的信息后,hello world 就会停止,但有些容器不会自动停止,需要手动让它终止
$ docker container kill [containID]
# 终止运行的容器文件,依然会占据硬盘空间,使用rm删除
$ docker container rm -f [containerID]
- docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
27dfef4ff215 hello-world "/hello" 20 minutes ago Exited (0) 20 minutes ago romantic_haibt
5e2962fe617b nginx "nginx -g 'daemon of…" 16 hours ago Created webserver
24826d2c60fe hello-world "/hello" 16 hours ago Exited (0) 16 hours ago suspicious_stallman
6c0cf43f6a08 ubuntu "bash" 16 hours ago Exited (255) About an hour ago gifted_hopper
04f15a35f737 hello-world "/hello" 16 hours ago Exited (0) 16 hours ago vigorous_goldwasser
830d2efcbab8 docker "docker-entrypoint.s…" 16 hours ago Exited (1) 16 hours ago loving_darwin
8cb11c08eb7d learn/tutorial "echo 'hello world'" 17 hours ago Exited (0) 17 hours ago pensive_raman
4a8dd3b1ab62 nginx "nginx -g 'daemon of…" 18 hours ago Exited (255) About an hour ago 0.0.0.0:80->80/tcp websever
c7106877f963 hello-world "/hello" 18 hours ago Exited (0) 18 hours ago cocky_lewin
Dockerfile 文件
制作 image 文件。
学会使用 image 文件以后,接下来的问题就是,如何可以生成 image 文件?如果你要推广自己的软件,势必要自己制作 image 文件。
这就需要用到 Dockerfile 文件。它是一个文本文件,用来配置 image。Docker 根据 该文件生成二进制的 image 文件。
创建Dockerfile文件
下面将介绍如何通过Dockerfile的定义文件和docker build命令来构建镜像。
Dockerfile使用基本的基于DSL语法的指令来构建一个Docker镜像,之后使用docker build命令基于该Dockerfile中的指令构建一个新的镜像。
# mkdir /opt/static_web
# cd /opt/static_web/
# vim Dockerfile
首先创建一个名为static_web的目录用来保存Dockerfile,这个目录就是我们的构建环境(build environment),Docker则称此环境为上下文(context)或者构建上下文(build context)。Docker会在构建镜像时将构建上下文和该上下文中的文件和目录上传到Docker守护进程。这样Docker守护进程就能直接访问你想在镜像中存储的任何代码、文件或者其他数据。这里我们还创建了一个Dockerfile文件,我们将用它构建一个能作为Web服务器的Docker镜像。
直接将这一段粘贴到Dockerfile中:
# Version: 0.0.1
FROM ubuntu:latest
MAINTAINER Bourbon Tian "bourbon@1mcloud.com"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Hi, I am in your container' > /usr/share/nginx/html/index.html
EXPOSE 80
Dockerfile由一系列指令和参数组成。每条指令都必须为大写字母,切后面要跟随一个参数。Dockerfile中的指令会按照顺序从上到下执行,所以应该根据需要合理安排指令的顺序。每条指令都会创建一个新的镜像层并对镜像进行提交。Docker大体上按照如下流程执行Dockerfile中的指令。
Docker从基础镜像运行一个容器。
执行第一条指令,对容器进行修改。
执行类似docker commit的操作,提交一个新的镜像层。
Docker再基于刚提交的镜像运行一个新的容器。
执行Dockerfile中的下一条命令,直到所有指令都执行完毕。
从上面可以看出,如果你的Dockerfile由于某些原因(如某条指令失败了)没有正常结束,那你也可以得到一个可以使用的镜像。这对调试非常有帮助:可以基于该镜像运行一个具备交互功能的容器,使用最后创建的镜像对为什么你的指令会失败进行调试。
Dockerfile也支持注释。以#开头的行都会被认为是注释,# Version: 0.0.1这就是个注释
FROM:
每个Dockerfile的第一条指令都应该是FROM。FROM指令指定一个已经存在的镜像,后续指令都是将基于该镜像进行,这个镜像被称为基础镜像(base iamge)。在这里ubuntu:latest就是作为新镜像的基础镜像。也就是说Dockerfile构建的新镜像将以ubuntu:latest操作系统为基础。在运行一个容器时,必须要指明是基于哪个基础镜像在进行构建。MAINTAINER:
MAINTAINER指令,这条指令会告诉Docker该镜像的作者是谁,以及作者的邮箱地址。这有助于表示镜像的所有者和联系方式RUN:
在这些命令之后,我们指定了三条RUN指令。RUN指令会在当前镜像中运行指定的命令。这里我们通过RUN指令更新了APT仓库,安装nginx包,并创建了一个index.html文件。像前面说的那样,每条RUN指令都会创建一个新的镜像层,如果该指令执行成功,就会将此镜像层提交,之后继续执行Dockerfile中的下一个指令。
默认情况下,RUN指令会在shell里使用命令包装器/bin/sh -c 来执行。如果是在一个不支持shell的平台上运行或者不希望在shell中运行(比如避免shell字符串篡改),也可以使用exec格式的RUN指令,通过一个数组的方式指定要运行的命令和传递给该命令的每个参数:
RUN ["apt-get", "install", "-y", "nginx"]
- EXPOSE指令
是告诉Docker该容器内的应用程序将会使用容器的指定端口。这并不意味着可以自动访问任意容器运行中服务的端口。出于安全的原因,Docker并不会自动打开该端口,而是需要你在使用docker run运行容器时来指定需要打开哪些端口。
可以指定多个EXPOSE指令来向外部公开多个端口,Docker也使用EXPOSE指令来帮助将多个容器链接,在后面的学习过程中我们会接触到。
基于Dockerfile构建新镜像
执行docker build命令时,Dockerfile中的所有指令都会被执行并且提交,并且在该命令成功结束后返回一个新镜像。
# cd static_web
# docker build -t="test/static_web" .
也可以在构建镜像的过程当中为镜像设置一个标签:
# docker build -t="test/static_web:v1" .
上面命令中最后的“.”告诉Docker到当前目录中去找Dockerfile文件。也可以指定一个Git仓库地址来指定Dockerfile的位置,这里Docker假设在Git仓库的根目录下存在Dockerfile文件:
# docker build -t="test/static_web:v1" git@github.com:test/static_web
- 构建缓存:
在上面执行构建镜像的过程中,我们发现当执行apt-get update时,返回Using cache。Docker会将之前的镜像层看做缓存,因为在安装nginx前并没有做其他的修改,因此Docker会将之前构建时创建的镜像当做缓存并作为新的开始点。然后,有些时候需要确保构建过程不会使用缓存。可以使用docker build 的 --no-cache标志。
docker build --no-cache -t="test/static_web" .
- 查看新镜像:
现在来看一下新构建的镜像,使用docker image命令,如果想深入探求镜像如何构建出来的,可以使用docker history命令看到新构建的test/static_web镜像的每一层,以及创建这些层的Dockerfile指令。
# docker images test/static_web
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
test/static_web latest 94728651ce15 20 hours ago 212.1 MB
# docker history 94728651ce15
IMAGE CREATED CREATED BY SIZE COMMENT
94728651ce15 20 hours ago /bin/sh -c #(nop) EXPOSE 80/tcp 0 B
09e999b131f4 20 hours ago /bin/sh -c echo 'Hi, I am in your container' 27 B
4af2ef04fb91 20 hours ago /bin/sh -c apt-get install -y nginx 56.52 MB
e98d2c152d1d 20 hours ago /bin/sh -c apt-get update 38.29 MB
从新镜像启动容器
下面基于新构建的镜像启动一个新容器,来检查之前的构建工作是否一切正常:
# docker run -d -p 80 --name static_web test/static_web nginx -g "daemon off;"
- -d选项,告诉Docker以分离(detached)的方式在后台运行。这种方式非常适合运行类似Nginx守护进程这样的需要长时间运行的进程。
- 这里也指定了需要在容器中运行的命令:nginx -g "daemon off;"。这将以前台运行的方式启动Nginx,来作为我们的Web服务器。
- -p选项,控制Docker在运行时应该公开哪些网络端口给外部(宿主机)。运行一个容器时,Docker可通过两种方法在宿主机上分配端口。
Docker可以在宿主机上通过/proc/sys/net/ipv4/ip_local_port_range文件随机一个端口映射到容器的80端口。
-可以在Docker宿主机中指定一个具体的端口号来映射到容器的80端口上。
这将在Docker宿主机上随机打开一个端口,这个端口会连接到容器中的80端口上。可以使用docker ps命令查看容得的端口分配情况:
# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0b422bbcce10 test/static_web "nginx -g 'daemon of 5 seconds ago Up 5 seconds 0.0.0.0:32772->80/tcp static_web
运行自己的Docker Registry
前面我们已经介绍了Docker有公共的Docker Registry就是Docker Hub。但是有时我们可能希望构建和存储包含不想被公开的信息或数据的镜像。这时候我们有以下两种选择:
- 利用Docker Hub上的私有仓库;
- 在防火墙后面运行自己的Registry。
从Docker容器安装一个Registry非常简单
## 拉去registry镜像
# docker pull registry
## 搭建本地镜像源
# docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 --restart=always --name registry registry:latest
## 查看容器状态
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f570fab5d67d registry:latest "/entrypoint.sh /etc 3 seconds ago Up 3 seconds 0.0.0.0:5000->5000/tcp registry
接下来将我们的镜像上传到本地的Docker Registry
## 找到我们要上传的镜像
# docker images test/apache2
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
test/apache2 latest 9c30616364f4 7 days ago 254.4 MB
## 使用新的Registry给该镜像打上标签
# docker tag 9c30616364f4 docker.example.com:5000/test/apache2
## 通过docker push 命令将它推送到新的Registry中去
# docker push docker.example.com:5000/test/apache2
The push refers to a repository [docker.example.com:5000/test/apache2] (len: 1)
9c30616364f4: Image already exists
f5bb94a8fac4: Image successfully pushed
2e36b30057ab: Image successfully pushed
0346cecb4e51: Image successfully pushed
274da7f89b05: Image successfully pushed
b5ce920a148c: Image successfully pushed
576b12d1aa01: Image successfully pushed
Digest: sha256:0c22a559f8dea881bca046e0ca27a01f73aa5f3c153b08b8bdf3306082e48b72
## 测试我们上传的镜像
# docker run -it docker.example.com:5000/test/apache2 /bin/bash
root@5088a0fd20e8:/#
基本概念
- 镜像是一种轻量级、可执行的独立软件包,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。
- 容器是镜像的运行时实例 - 实际执行时镜像会在内存中变成什么。默认情况下,它完全独立于主机环境运行,仅在配置为访问主机文件和端口的情况下才执行此操作。
容器在主机内核上以本机方式运行应用。与仅通过管理程序对主机资源进行虚拟访问的虚拟机相比,它们具有更好的性能特征。容器可以获取本机访问,每个容器都在独立进程中运行,占用的内存不超过任何其他可执行文件。
虚拟机(virtual machine)
就是带环境安装的一种解决方案。它可以在一种操作系统里面运行另一种操作系统,比如在 Windows 系统里面运行 Linux 系统。应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。
虽然用户可以通过虚拟机还原软件的原始环境。但是,这个方案有几个缺点。
(1)资源占用多
虚拟机会独占一部分内存和硬盘空间。它运行的时候,其他程序就不能使用这些资源了。哪怕虚拟机里面的应用程序,真正使用的内存只有 1MB,虚拟机依然需要几百 MB 的内存才能运行。
(2)冗余步骤多
虚拟机是完整的操作系统,一些系统级别的操作步骤,往往无法跳过,比如用户登录。
(3)启动慢
启动操作系统需要多久,启动虚拟机就需要多久。可能要等几分钟,应用程序才能真正运行。
Linux 容器
由于虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)。
Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
由于容器是进程级别的,相比虚拟机有很多优势。
(1)启动快
容器里面的应用,直接就是底层系统的一个进程,而不是虚拟机内部的进程。所以,启动容器相当于启动本机的一个进程,而不是启动一个操作系统,速度就快很多。
(2)资源占用少
容器只占用需要的资源,不占用那些没有用到的资源;虚拟机由于是完整的操作系统,不可避免要占用所有资源。另外,多个容器可以共享资源,虚拟机都是独享资源。
(3)体积小
容器只要包含用到的组件即可,而虚拟机是整个操作系统的打包,所以容器文件比虚拟机文件要小很多。
总之,容器有点像轻量级的虚拟机,能够提供虚拟化的环境,但是成本开销小得多。