Fork me on GitHub

Docker基础:镜像、容器、仓库

  Mac安装使用Docker

什么是Docker

  Docker 是一个 开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源。
  Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。”一次封装,到处运行
  解决了运行环境和配置问题软件容器,方便做持续集成并有助于整体发布的容器虚拟化技术。

  容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。
  1.不同的应用程序可能会有不同的应用环境,比如.net开发的网站和php开发的网站依赖的软件就不一样,如果把他们依赖的软件都安装在一个服务器上就要调试很久,而且很麻烦,还会造成一些冲突。比如IIS和Apache访问端口冲突。这个时候你就要隔离.net开发的网站和php开发的网站。常规来讲,我们可以在服务器上创建不同的虚拟机在不同的虚拟机上放置不同的应用,但是虚拟机开销比较高。docker可以实现虚拟机隔离应用环境的功能,并且开销比虚拟机小,小就意味着省钱了。
  2.你开发软件的时候用的是Ubuntu,但是运维管理的都是centos,运维在把你的软件从开发环境转移到生产环境的时候就会遇到一些Ubuntu转centos的问题,比如:有个特殊版本的数据库,只有Ubuntu支持,centos不支持,在转移的过程当中运维就得想办法解决这样的问题。这时候要是有docker你就可以把开发环境直接封装转移给运维,运维直接部署你给他的docker就可以了。而且 部署速度快
  3.在服务器负载方面,如果你单独开一个虚拟机,那么虚拟机会占用空闲内存的,docker部署的话,这些内存就会利用起来。总之docker就是集装箱原理。

Docker 的优点

  1.简化程序:
  Docker 让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,便可以实现虚拟化。Docker改变了虚拟化的方式,使开发者可以直接将自己的成果放入Docker中进行管理。方便快捷已经是 Docker的最大优势,过去需要用数天乃至数周的 任务,在Docker容器的处理下,只需要数秒就能完成。
  2.避免选择恐惧症:
  如果你有选择恐惧症,还是资深患者。Docker 帮你 打包你的纠结!比如 Docker 镜像;Docker 镜像中包含了运行环境和配置,所以 Docker 可以简化部署多种应用实例工作。比如 Web 应用、后台应用、数据库应用、大数据应用比如 Hadoop 集群、消息队列等等都可以打包成一个镜像部署。
  3.节省开支:
  一方面,云计算时代到来,使开发者不必为了追求效果而配置高额的硬件,Docker 改变了高性能必然高价格的思维定势。Docker 与云的结合,让云空间得到更充分的利用。不仅解决了硬件管理的问题,也改变了虚拟化的方式。

Docker 架构

  Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。
  Docker 容器通过 Docker 镜像来创建。
  容器与镜像的关系类似于面向对象编程中的对象与类。

Docker 面向对象
镜像
容器 对象

  
类似git
  Docker基本组成
  - 镜像(Images)
  - 容器(Container)
  - 仓库(Registry)

名称: 说明
Docker镜像(Images)  Docker 镜像是用于创建 Docker 容器的模板。
Docker 容器(Container) 容器是独立运行的一个或一组应用。
Docker 客户端(Client) Docker 客户端通过命令行或者其他工具使用 Docker API (https://docs.docker.com/reference/api/docker_remote_api) 与 Docker 的守护进程通信。
Docker主机(Host) 一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。
Docker仓库(Registry) Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。Docker Hub(https://hub.docker.com) 提供了庞大的镜像集合供使用。
Docker Machine Docker Machine是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker,比如VirtualBox、 Digital Ocean、Microsoft Azure。

底层原理

Docker是怎么工作的

  Docker 是一个Client-Server结构的系统,Docker守护进程运行在主机上,然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器。容器,是一个运行环境,就是我们前面说到集装箱。
  

为什么Docker比VM快

  1).docker有着比虚拟机更少的抽象层。由于docker不需要Hypevisor实现硬件资源虚拟化,运行在docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上docker将会效率上有明显优势。
  2).docker利用的是宿主机的内核,而不需要Guest OS。因此,当新建一个容器时,docker不需要和虚拟机一样重新加载一个操作系统内核。从而避免引寻、加载操作系统内核返回比较费时资源的过程,当新建一个虚拟机时,虚拟机软件需要加载Guest OS,返回新建过程是分钟级别的。而docker由于直接利用宿主机的操作系统,则省略了返回过程,因此新建一个docker容器只需要几秒钟。
  可以把容器看做是一个简易版的Linux环境
  

Docker常用命令

  Docker 命令大全

重要命令

1.新建并启动容器:
  启动交互式容器:docker run -it centos
  启动守护式容器:docker run -d centos

  使用镜像centos:lates,以后台模式启动一个容器:docker run -d centos
  问题:然后docker ps -a进行查看,会发现容器已经退出
  很重要的要说明一点:Docker容器后台运行,就必须有一个前台进程。
  容器运行的命令如果不是那些 一直挂起的命令(如运行top, tail),就是会自动退出的。
  这个是docker的机制问题。
  比如你的web容器,以nginx为例,正常情况下,我们配置启动服务只需要启动响应的service即可。例如,service nginx start。但这样做,nginx为后台进程模式运行,就导致docker前台没有运行的应用,这样的容器后台启动后,会立即自杀,因为他觉得他没事可做了。
  所以,最佳的解决方案是,将你要运行的程序以前台进程的形式运行。如 docker run -d centos /bin/sh -c “while true; do echo hello; sleep 2; done”
  
2.查看容器日志:docker logs -f -t --tail 容器ID
  -t 是加入时间戳
  -f 跟随最新的日志打印
  –tail 数字 显示最后多少条

3.查看容器内运行的进程:docker top 容器ID
4.查看容器内部细节:docker inspect 容器ID

5.退出容器:
  - exit: 容器停止退出
  - ctrl+p+q: 容器不停止退出

6.查看正在运行的容器并以命令行交互
  - docker exec -it 容器ID bashShell
  - 重新进入 docker attach 容器ID
  exec 是在容器中打开新的终端,并且可以启动新的进程,功能更强大些。docker exec -it 容器ID /bin/bash
  attach 直接进入容器启动命令的终端,不会启动新的进程。

7.从容器内拷贝文件到主机上:docker cp 容器ID:容器内路径 目的的主机路径


Docker镜像

  镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。

UnionFS(联合文件系统)

  UnionFS: Union文件系统是一种分层、轻量级并且高性能的文件系统,它支持 对文件系统的修改作为一次提交来一层层的叠加, 同时可以将不同目录挂载到同一个虚拟文件系统下。Union文件系统是Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。有点像花卷
  特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。

Docker镜像加载原理

  docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。
  bootfs (boot file system)主要包含bootloader和kernel,bootloader主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
  rootfs (root file system),在bootfs之上。包含的就是典型Linux系统中的/dev,/proc,/bin,/etc等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubutu,Centos等。
  
  对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用Host和kernel,自己只需要提供rootfs就行了。由此可见对于不同的linux发行版,bootfs基本是一致的,rootfs会有差别,因此不同的发行版可以 公用bootfs
  

分层的镜像

为什么例如tomacat的镜像会有这么大?

1
2
3
docker@boot2docker$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat latest b8efb18f159b 1 months ago 462.6 MB

  基于UnionFS(联合文件系统)的分层原理
  

为什么Docker镜像要采用这种分层结构呢?

  最大的一个好处就是:共享资源
  比如:有多个镜像都从相同的base镜像构建而来,那么宿主机只需要在磁盘上保存一份base镜像,同时内存中也只需加载一份base镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。

特点

  Docker镜像都是只读的。
  当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”


Docker容器数据卷

是什么

  先来看看Docker的理念:
  - 将运用与运行的环境打包形成容器运行,运行可以伴随容器,但是我们对数据的要求希望是持久化的。
  - 容器之间希望有可能共享数据
  
  Docker容器产生的数据,如果不通过docker commit生成新的镜像,使得数据做为镜像的一部分保存下来,那么当容器删除后,数据自然也就没有了。
  为了能保存数据做持久化,在docker中我们使用卷。
  一句话:有点类似我们Redis里面的rdb和aof文件

能干嘛

  容器的持久化
  容器间继承 + 共享数据

  


实例

  过去,如果要开始编写一个Python应用程序,首先是安装Python。但是,这会造成在其他机器上的环境必须如此。
  使用Docker,您可以将便携式(portable)的Python作为镜像运行,无需安装。然后,构建基本的Python镜像与应用程序代码,确保您的应用程序,其依赖关系在运行时能一起运行。
  这些镜像由Dockerfile定义。

用Dockerfile定义一个容器

  Dockerfile将定义容器内的环境中发生的情况。访问资源(如网络接口和磁盘驱动器)在此环境中进行虚拟化,这与您系统的其余部分是隔离的,因此您必须将端口映射到外部世界,并具体说明要将哪些文件“复制”到那个环境。这样做之后,Dockerfile构建的应用程序将能在任何地方运行。

  将下面三个文件放到同一个文件夹里
  3-1.Dockerfile
  创建一个空目录。将目录更改到新目录中,创建一个名为“Dockerfile”的文件,将以下内容复制并粘贴到该文件中,并将其保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Use an official Python runtime as a parent image
FROM python:2.7-slim
# Set the working directory to /app
WORKDIR /app
# Copy the current directory contents into the container at /app
ADD . /app
# Install any needed packages specified in requirements.txt
RUN pip install -r requirements.txt
# Make port 80 available to the world outside this container
EXPOSE 80
# Define environment variable
ENV NAME World
# Run app.py when the container launches
CMD ["python", "app.py"]

  3-2.requirements.txt

1
2
Flask
Redis

  3-3.app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from flask import Flask
from redis import Redis, RedisError
import os
import socket
# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)
app = Flask(__name__)
@app.route("/")
def hello():
try:
visits = redis.incr("counter")
except RedisError:
visits = "<i>cannot connect to Redis, counter disabled</i>"
html = "<h3>Hello {name}!</h3>" \
"<b>Hostname:</b> {hostname}<br/>" \
"<b>Visits:</b> {visits}"
return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)

  现在我们看到,pip install -r requirements.txt为Python安装Flask和Redis库,应用程序会打印环境变量NAME以及调用的输出socket.gethostname()。
  最后,因为Redis没有运行(因为我们只安装了Python库,而不是Redis本身),我们应该期望在这里使用它的尝试将失败并产生错误消息。
  您不需要在系统里安装Python或requirements.txt里的库,也不用构建或运行此镜像安装在您的系统上。似乎并没有真正使用Python和Flask来设置一个环境,但是实际你已经有了。

编译得到镜像

1
2
$ ls
Dockerfile app.py requirements.txt

  运行build命令。这将创建一个Docker镜像,我们使用-t将它标记,friendlyhello为镜像名称,最后还有一个”.”,这是用来指明Dockerfile路径的,”.”表示当前路径下。

1
$ docker build -t friendlyhello .

  你的图像在哪里?它在您机器的本地Docker镜像注册表中:

1
2
3
$ docker images
REPOSITORY TAG IMAGE ID
friendlyhello latest 326387cea398

运行

  运行应用程序,
  -p 是端口映射,将本机的4000端口映射到容器的80.
  -d 让程序在后台运行

1
docker run -d -p 4000:80 friendlyhello --name myfridend

  可以在网页上输入 http://192.168.59.103:4000 显示内容。(192.168.59.103这个IP是在Mac系统上VirtualBox的boot2docker的ip,如果系统是Linux/Unix,直接用localhost)
  也可以可以使用curl命令来查看相同的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ boot2docker ip
192.168.59.103
$ curl http://192.168.59.103:4000
<h3>Hello World!</h3><b>Hostname:</b> 8fc990912a14<br/><b>Visits:</b> <i>cannot connect to Redis, counter disabled</i>
$ docker ps
CONTAINER ID IMAGE COMMAND NAMES
1fa4ab2cf395 friendlyhello "python app.py" myfridend
你会看到CONTAINER ID匹配http://localhost:4000。
# stop:停止一个运行中的容器, 类似的有start/restart
$ docker stop 1fa4ab2cf395

分享镜像

  为了演示我们刚刚创建的可移植性,让我们上传我们build的映像,并能在其他地方运行它。毕竟,当您要将容器部署到生产中时,您将需要了解如何推送到注册表(you’ll need to learn how to push to registries when you want to deploy containers to production.)。
  (A registry is a collection of repositories, and a repository is a collection of images)一个注册用户有很多个存储库,一个存储库有很多镜像, 类似于代码已经被构建的GitHub存储库。注册的用户可以创建许多存储库。The docker CLI uses Docker’s public registry by default。

注意:我们将使用Docker的public registry,因为它是免费的和预配置的。but there are many public ones to choose from, and you can even set up your own private registry using Docker Trusted Registry.

Docker ID登录
  如果您没有Docker帐户,请在cloud.docker.com注册一个 。记下您的用户名。

1
2
# 登录
$ docker login

Tag the image
  标签是可选的:

1
2
3
4
5
6
7
8
9
10
# 该命令的语法是
# docker tag image username/repository:tag
# 例如:
$ docker tag friendlyhello john/get-started:part1
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
friendlyhello latest d9e555c53008 3 minutes ago 195MB
john/get-started part1 d9e555c53008 3 minutes ago 195MB

Publish(发布) the image
  将标记的镜像上传到存储库:命令格式如下:docker push username/repository:tag
  一旦完成,此上传的结果是公开的,可以登陆查看。也可以使用pull命令获取该镜像。

Pull and run the image from the remote repository
  您可以使用docker run在任何机器上运行,使用此命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
docker run -p 4000:80 username/repository:tag
如果图像在本机不可用,Docker将从存储库中拉出。
$ docker run -p 4000:80 john/get-started:part1
Unable to find image 'john/get-started:part1' locally
part1: Pulling from orangesnap/get-started
10a267c67f42: Already exists
f68a39a6a5e4: Already exists
9beaffc0cf19: Already exists
3c1fe835fb6b: Already exists
4c9f1fa8fcb8: Already exists
ee7d8f576a14: Already exists
fbccdcced46e: Already exists
Digest: sha256:0601c866aab2adcc6498200efd0f754037e909e5fd42069adeff72d1e2439068
Status: Downloaded newer image for john/get-started:part1

注意:如果没有指定:tag这些命令的部分,:latest则将在构建时和运行映像时假定标记。Docker将使用运行的最后一个版本的图像,而没有指定标签(不一定是最新的图像).

   无论在哪执行docker run,它都会将您的图像、Python和requirements.txt所依赖的关系从您的代码中提取出来,并运行您的代码。它们被打包在一起,主机不需要安装任何东西,Docker就能运行它。
   In the next section, we will learn how to scale(衡量) our application by running this container in a service。

-----------------本文结束,感谢您的阅读-----------------