40

Docker是怎么出现的

关于Docker的发展史,本文就不做介绍,有兴趣的小伙伴们可以查看这篇文章,挺有意思的。http://www.oschina.net/news/5...

什么是Docker?

  在Docker之前,我们肯定要先了解Docker是什么。官网的介绍是“Docker is the world’s leading software container platform.”官方给Docker的定位是一个应用容器平台。至于为什么要做这个Docker,官网上还有这么一句话"Docker is an open platform for developers and sysadmins to build, ship, and run distributed applications, whether on laptops, data center VMs, or the cloud."这句话用一句非常简单的话去概括就是"Build once,Run anyWhere".这一点跟Java很像。那么它这样做是要解决现实中什么问题,我列举几个情况。
   1.合作开发的时候,在本机可以跑,别人的电脑跑不起来
   这里我们拿java Web应用程序举例,我们一个java Web应用程序涉及很多东西,比如jdk、tomcat、spring等等。当这些其中某一项版本不一致的时候,可能就会导致应用程序跑不起来这种情况。Docker则将程序直接打包成镜像,直接运行在容器中即可。
   2.服务器自己的程序挂了,结果发现是别人程序出了问题把内存吃完了,自己程序因为内存不够就挂了
  这种也是一种比较常见的情况,如果你的程序重要性不是特别高的话,公司基本上不可能让你的程序独享一台服务器的,这时候你的服务器就会跟公司其他人的程序共享一台服务器,所以不可避免地就会受到其他程序的干扰,导致自己的程序出现问题。Docker就很好解决了环境隔离的问题,别人程序不会影响到自己的程序。
  3.公司要弄一个活动,可能会有大量的流量进来,公司需要再多部署几十台服务器
  在没有Docker的情况下,要在几天内部署几十台服务器,这对运维来说是一件非常折磨人的事,而且每台服务器的环境还不一定一样,就会出现各种问题,最后部署地头皮发麻。用Docker的话,我只需要将程序打包到镜像,你要多少台服务,我就给力跑多少容器,极大地提高了部署效率。

Docker与虚拟机的区别

关于Docker与虚拟机的区别,我在网上找到的一张图,非常直观形象地展示出来,话不多说,直接上图。

图片描述

图片描述

  比较上面两张图,我们发现虚拟机是携带操作系统,本身很小的应用程序却因为携带了操作系统而变得非常大,很笨重。Docker是不携带操作系统的,所以Docker的应用就非常的轻巧。另外在调用宿主机的CPU、磁盘等等这些资源的时候,拿内存举例,虚拟机是利用Hypervisor去虚拟化内存,整个调用过程是虚拟内存->虚拟物理内存->真正物理内存,但是Docker是利用Docker Engine去调用宿主的的资源,这时候过程是虚拟内存->真正物理内存。

Docker安装

  Docker的安装非常简单,官网上都给出了具体的安装步骤,都是可视化的安装,三步就搞定了。这里我就不细说了,附上Docker的官网安装教程地址。
Mac:https://docs.docker.com/docke...
Windows:https://docs.docker.com/docke...
linux的话,官网有不同版本的安装的教程,这里放上ubuntu的安装教程,其他小伙们自行查看。
ubuntu:https://docs.docker.com/engin...

Docker三个基本概念

  下面这张图非常的经典,很形象地展示了,什么是容器,什么是镜像,什么是仓库,以及三者之间的联系。
图片描述

  接下来我们来解释一下这张图。现在我们要造一间厨房,在造之前我们首先要干的一件事,就是先列举出我们造厨房需要的东西。我们可能需要一个通了水电煤的房子以及一些必需的厨房用具诸如锅碗瓢勺、煤气灶、冰箱、水槽等等这些东西。现在我们知道需要了什么东西之后,我们就去找这些东西。首先我们先去京东购买一些厨房用具,这些用具就好比我们的Docker镜像,我们厨房的用具到了之后得找个地方把它们放在,不可能随处丢吧,不然后面用的时候就找不到它了,那么我们Docker镜像也是这样,需要一个Docker仓库去存储这些镜像。现在我们有了这些厨房用具之后就可以做饭了吗?答案当然是不能,没水没电没火啊!这时候我们得把厨房用具给装到一个通了水电煤的房子才行,那么Docker镜像也是这样,单纯的Docker镜像是不能用的,它得装到Docker容器中通了水电煤才能使用。等我们装好了厨房用具之后我们就可以开始做饭,那么我们的Docker镜像装到Docker容器之后,我们应用就可以跑起来了。

我的第一个Docker镜像

上面的文章中,我们了解了一下Docker,接下来我们学着做一个属于自己的Docker镜像。

第一步:从镜像中心下载一个Node镜像

    a).去http://hub.daocloud.io/上找到Node镜像地址并执行如下命令
      docker pull daocloud.io/library/node:标签
    b).查看本地库的Docker镜像,是否下载完成
      docker images

结果如下:
图片描述

第二步:写一个app.js

app.js的内容如下,内容很简单,作用也很简单,起一个80端口的服务,页面显示“Hello Docker”。


var http = require('http');

http.createServer(function (request, response) {

    // 发送 HTTP 头部
    // HTTP 状态值: 200 : OK
    // 内容类型: text/plain
    response.writeHead(200, {'Content-Type': 'text/plain'});

    // 发送响应数据 "Hello World"
    response.end('Hello Docker\n');
}).listen(3000);

// 终端打印如下信息
console.log('Server running at http://127.0.0.1:3000/');

第三步:写一个Dockerfile,用于构建镜像

#依赖的镜像
FROM daocloud.io/library/node:latest
#镜像创建者的信息
MAINTAINER wuming "wuming@maihaoche.com"
#执行mkdir helloDocker创建一个helloDocker文件夹
RUN mkdir helloDocker
#将app.js添加到helloDocker文件夹中
ADD app.js  helloDocker
#容器运行时启动的命令,下面命令等价于 CMD node /helloDocker/app.js
CMD ["node","/helloDocker/app.js"]

第四步:执行命令,构建对象

docker build -t hello-docker .
//-t 用于指定镜像的name:tag,没有指定tag则是latest。
//. 代表Dockerfile 所在的路径,这里是当前目录

结果如下:
图片描述

如果镜像仓库或者标签错了的话,可以使用如下命令进行修改

    docker tag IMAGE ID name:tag
   例: docker tag 1786dad83d25 hello-docker:1.0.0

结果如下:
图片描述
这里需要注意的是“docker tag”是新建一个标签用于目标镜像指向源镜像,所以我们记得删除原来的标签

docker rmi name:tag
例:docker rmi hello-docker:latest

图片描述

温馨提示:当前目录应该有Dockerfile和app.js才可用上面命令构建成功
图片描述

我的第一个Docker容器

docker run  -d -p 8003:3000 IMAGE ID

注意事项:
-p IP:host_port:container_port-p IP::port 来指定允许访问容器的主机上的 IP、接口等,我这里只允许外部8080端口来访问3000端口
-d 就是在后台运行容器,并返回容器ID。有兴趣的小伙伴可以体验一下有-d和没-d的区别

这时我们输入http://localhost:8003/就可以访问了我们刚刚启的服务了。
图片描述

另外我们可以通过docker ps 来查看当前所有启动的容器。
接下来我们进入容器中看看这个容器到底有什么东西!

docker exec -it <CONTAINER ID 或者 NAMES> /bin/bash

图片描述

进入容器中我们就看到了刚刚我们之前在Dockerfile建的helloDocker文件夹,进去之后发现了我们的app.js.执行exit命令就可以退出容器。

Docker 数据卷的使用

  到这里我们已经掌握了如何去制作一个镜像以及如何启动一个镜像了,接下来我们要说点难一点的东西Docker数据卷的使用。首先说一下什么是卷,为什么要引入卷这个东西。
  这边我们拿前端开发来说,我们在前端开发中,我们的js、css以及一些图标等等这些静态资源,这些文件都是可以直接在url输入地址直接访问到的,本身这些东西不会影响项目的运行,说点通俗点就是我们的这些资源怎么整不会让服务器报500错误.按照我们前面的方法去做的话,肯定之间把这些文件全部打到镜像里面,但是这个方法有个很不好的地方的。就是我们每次修改他们都需要重新打一次镜像,就感觉很繁琐。于是我们就想,我们能不能把这次静态资源从镜像中抽离出来,我们让容器指向这个目录,然后我们的服务就可以访问这些资源,每次改变完之后我们就不需要重新打镜像了,这样岂不是很好。正因为如此我们的数据卷就出来了,用来解决这个问题,那么什么是数据卷呢?我们用一句话来阐述就是“数据卷是一个可供一个或多个容器使用的特殊目录。”接下来我们实际操作一把。

数据卷

第一步:我们建立一个myDocker文件,里面包含两个文件app.js和package.json

app.js的内容如下:

var express = require('express');
var app = express();
//设置public目录为静态目录
app.use(express.static('public'));
//监听3000端口
app.listen(3000);

package.json内容如下(主要目的是安装express,自己可以npm init自己生成一下,不必抄我的):

{
  "name": "docker",
  "version": "1.0.0",
  "description": "docker test",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.15.3"
  }
}

第二步:在myDocker同级目录下,新建一个public目录,放张图片进去,这里我放了一张美女图片(hhh)。

图片描述

第三步:在myDocker同级目录下新建一个Dockerfile,内容如下:

#设置基础镜像
FROM daocloud.io/library/node:latest
#维护者信息
MAINTAINER wuming wuming@maihaoche.com
#在容器中新建一个myDocker文件中
RUN mkdir myDocker
#将Dockerfile所在目录中myDocker文件夹的内容加到目标容器中的myDocker文件夹中
ADD myDocker  /myDocker
#设置工作目录
WORKDIR /myDocker
#执行安装项目依赖包的命令
RUN npm install
#容器启动时,执行node app.js
CMD node app.js

第四步:构建一个名为my-docker的镜像

当前目录结构为:
myDocker
  ----Dockerfile
  ----myDocker
    ----app.js
    ----package.json
  ----public
构建命令

docker build -t my-docker:latest .

第五步:以挂载方式启动容器

 docker run -d -p 8003:3000 -v /Users/wuming/dockerWorkpace/myDocker/public:/myDocker/public  my-docker:latest

这里我将本机的myDocker下的public挂载到容器中的/myDocker/public下,这里我们可以登入容器中看一下,会发现我们镜像并没有将我们的public目录打包进去,但是我们的容器中却有这个public目录。
图片描述

第六步:访问我们的图片

在游览器地址栏中输入http://localhost:8003/test.jpg便可看见,如下效果:图片描述
大家可以往自己的本机的public丢图片进去,再去访问它试试看,以及看看容器中的public没有动态改变,答案是可以的。

数据卷容器

上面我们已经实现了数据卷,但是我们发现加入如果我要起多个容器服务,时间短还能记得这个容器挂载目录,要是时间多了岂不是都忘了,而且每次这样去挂载也挺麻烦的,我们能不能把我们已经配好数据卷的容器作为一个数据卷的提供者呢?答案是可以的,这就是我们要说的数据卷容器,接下来我们操作一下。
docker run -d -p 8005:3000 --volumes-from NAMES或者CONTAINER ID  my-docker:latest
上面的NAMES或者CONTAINER ID为我们配好的数据卷的容器。启动完容器之后,我们可以访问试一下,并且往挂载目录丢点其他图片进去,看看两个容器服务是不是都可以访问到。答案是可以的。

使用网络

外部访问容器

1.映射所有接口地址
-p <端口号(8003)>:<端口号(3000)>

任何ip地址的指定<端口号(8003)>都可以访问到服务(前提是那ip地址对应的主机要起了这个服务)

2.映射到指定地址的指定端口
-p <ip地址(127.0.0.1)><端口号(8003)>:<端口号(3000)>

指定ip地址的<端口号(8003)>都可以访问到服务(前提是那ip地址对应的主机要起了这个服务),比如指定127.0.0.1,只有本机输入127.0.0.1:8003或者localhost:8003可以访问自己的服务,但是其他同一个局域网的手机输入本机的对应局域网的ip地址:8003是访问不到的,小伙们可以拿自己的手机试试。

Docker常用命令

1.杀死所有正在运行的容器

docker kill $(docker ps -a -q)

2.删除所有已经停止的容器

docker rm $(docker ps -a -q)

3.删除所有镜像

docker rmi $(docker images -q)

4.关闭容器

docker stop CONTAINER ID或者NAMES

5.重新启动关闭的容器

docker start CONTAINER ID或者NAMES

6.移除本地容器

docker rm CONTAINER ID或者NAMES

7.查看本地容器

docker ps  //查看正在运行的容器
docker ps -a //查看所有容器

8.查看本地镜像

 docker images

9.创建镜像

 docker build -t name:tag Dockerfile路径  

10.修改本地镜像标记

 docker tag  IMAGE ID  name:tag 
 docker rmi name:tag

11.删除本地镜像

docker rmi name:tag或者IMAGE ID

12.进入容器

docker exec -it IMAGE ID或者NAMES /bin/bash

13.获取镜像中心的镜像

docker pull name:tag

14.获取容器的端口映射配置

docker port CONTAINER ID或者NAMES

持续更新更新....

Dockerfile命令速查表

FROM 命令

FROM <image>:<tag>

用于设置基础镜像,一般是Dockerfile的第一句。
如果没有指定 tag ,则默认tag是latest。

MAINTAINER

MAINTAINER <name>

用来指定维护者的姓名和联系方式。

RUN

RUN <command> 或 RUN ["executable", "param1", "param2"]

每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像。

ADD

ADD <src> <dest>

将 <src> 文件复制到 <dst> 文件:<src> 是相对被构建的源目录的相对路径,可以是文件或目录的路径,也可以是一个远程的文件 url,<dst> 是容器中的绝对路径。

COPY

COPY <src> <dest>

复制本地主机的 <src> (为Dockerfile所在目录的相对路径)到容器中的 <dest>,与ADD指令差不多

ENTRYPOINT

ENTRYPOINT ["executable", "param1", "param2"] :推荐使用的 exec 形式

ENTRYPOINT command param1 param2 :shell 形式

配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。
一个 Dockerfile 中只能有一个 ENTRYPOINT,如果有多个,则最后一个生效。

CMD

CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;
CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;

指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。

如果用户启动容器时候指定了运行的命令,则会覆盖掉 CMD 指定的命令。

WORKDIR

WORKDIR /path/to/workdir

为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。

可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

则最终路径为 /a/b/c 。

EXPOSE

EXPOSE <port> [<port>...]

告诉 Docker 服务端容器暴露的端口号,供互联系统使用。
例如 EXPOSE 8080 3000,开放 8080 和 3000 端口。

ENV

ENV <key> <value>

指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。

VOLUME

VOLUME ["/data"]

创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。

USER

USER <UID/Username>

为容器内指定 CMD RUN ENTRYPOINT 命令运行时的用户名或UID。

ONBUILD

ONBUILD [INSTRUCTION]

配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
例如,利用Dockerfile创建了一个镜像A,其中Dockerfile中有这么几个命令

ONBUILD RUN mkdir test
ONBUILD ADD app.js /test/app.js

那么镜像B基于镜像A去构建的时候,默认会在最后加上这两行命令

 FROM 镜像A
 RUN mkdir test
 ADD app.js /test/app.js

wuming
3.2k 声望788 粉丝

Success is not final, failure is not fatal, it is the courage to continue that counts.