前言

在自己的公司工作之余,听到朋友说到了集群,然后不知道怎么的同学又简单的给我描述了一下这个所谓的docker容器怎么怎么的好。最吸引我的一项功能就是docker作为一个容器可以很简单那的在不同机器上部署环境,作为一个linux小白的我,作为一个为了部署环境重装两次系统的我,深深的感受到了这个集群带来的极大的便利。昨天新整了一个服务器,让我们在全新的服务器上进行操作(防止重装系统。。。)。

基本概念

既然我们需要部署环境,那么我们就得先了解一下这些具体是什么,具体是做些什么的。

集群的概念

集群(cluster)就是一组计算机,它们作为一个整体向用户提供一组网络资源。这些单个的计算机系统就是集群的节点(node)。一个理想的集群是,用户从来不会意识到集群系统底层的节点,在他/她们看来,集群是一个系统,而非多个计算机系统。并且集群系统的管理员可以随意增加和删改集群系统的节点。
集群的研究起源于集群系统的良好的性能可扩展性(scalability)。提高cpu主频和总线带宽是最初提供计算机性能的主要手段。但是这一手段对系统性能的提供是有限的。接着人们通过增加CPU个数和内存容量来提高性能,于是出现了向量机,对称多处理机(SMP)等。但是当CPU的个数超过某一阈值,象SMP这些多处理机系统的可扩展性就变的极差。主要瓶颈在于CPU访问内存的带宽并不能随着CPU个数的增加而有效增长。与SMP相反,集群系统的性能随着CPU个数的增加几乎是线性变化的。
几种计算机系统的可拓展性

容器的概念

容器的核心思想是操作系统内核的复用(一种虚拟化的方案),通过提供应用运行环境描述规范,自动构建应用(进程)的沙箱运行环境,达到应用相互隔离的目的。为服务器上运行的每个应用程序提供了独自、隔离的环境来运行,但是那些容器都共享主机服务器的操作系统。由于容器没很必要装入操作系统,你可以在一瞬间为虚拟机建立容器,而不是要花数分钟。
容器中的工作负载使用主机服务器的操作系统内核,避免像虚拟机一样从存储系统中进行检索文件。与虚拟机不同的是,虚拟机是通过中间层将一台或多台虚拟系统独立运行在硬件之上,而容器是运行在操作系统的内核之上的,可以称为操作系统虚拟化,故而只能运行相同或相似的内核操作系统。由于依赖于linux内核特性:Namespace和Cgroups,所以docker上只能运行linux操作系统

容器集群

当使用多个容器来部署应用,用来达到提高并发访问能力和避免单点故障的目的。这多个容器对外是作为一个整体提供服务,从而变成了集群。

docker

Docker 是一个开源的应用容器引擎(就是将应用程序自动部署到容器的技术),让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。
一个完整的Docker有以下几个部分组成:

  • dockerClient客户端
  • Docker Daemon守护进程
  • Docker Image镜像
  • DockerContainer容器

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

  • 文件系统隔离:每个容器有自己的root文件系统 进程隔离:每个容器都运行在自己的进程环境中
  • 网络隔离:容器间的虚拟网络接口和IP地址都是分开的
  • 资源隔离和分组:使用cgroups将cpu和内存之类的资源独立分配给每个Docker 容器

docker 环境部署

docker 安装

操作系统:Centos7
前置条件:

    64-bit 系统 (docker目前不支持32位内核)
    kernel 3.10+

首先,检查内核版本,返回的值大于3.10即可。

$ uname -r

其次,为了保证docker的正常使用,我们先将环境进行更新。
##这里需要提示一下,在centos里面是没有apt-get命令的,所以我们采用的yum。

yum -y update ######这是一个习惯性的问题,安装之前先更新
yum -y install docker-ce #####使用yum源进行安装docker
systemctl start docker #####安装完成后基本就可以启动了

配置mirror

//暂时不知道这个步骤有什么用 后期补充(不做这一步也是可以的)
新版的 Docker 使用 /etc/docker/daemon.json 来配置 Daemon
在该配置文件中加入(没有该文件的话,请先创建一个):
{
“registry-mirrors”: [“https://docker.mirrors.ustc.edu.cn”]
}
###详情在参考文档里面有

安装完成

安装完成之后测试

sudo docker run hello-world

最后成功则显示语句

Hello from Docker!
This message shows that your installation appears to be working correctly.

制作自己的docker镜像

构建Docker镜像有以下两种方法:

  • 使用docker commit命令。
  • 使用docker build命令和 Dockerfile 文件。

一般来说,不是真正的“创建”新镜像,而是基于一个已有的基础镜像,如ubuntu或centos等。想从头制作镜像点击这里

docker commit创建镜像

docker commit创建镜像可以被理解为在以往的系统版本里面提交更改。
我们先启动一个容器,然后在里面等待安装等操作完成之后,退出docker 运行docker commit命令。

docker commit 命令
#docker commit -m="A new custom image" --author="Bourbon Tian" b437ffe4d630 test/apache2:webserver
返回值
27fc508c41d1180b1a421380d755cf00f9dfb6b0d354b9eccaec94ae58a06675

这条命令里,我们指定了更多的信息选项:

  • -m 用来指定创建镜像的提交信息
  • –author 用来列出该镜像的作者信息
  • 在test/apache2后面增加了一个webserver标签

通过docker build命令和dDockerfile文件进行镜像搭建

创建Dockerfile文件

Dockerfile文件使用的基本是给予DSL语法的指令来构建docker镜像的,之后使用docker build命令基于该docker中的指令创建一个新的镜像。
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镜像。

# Version: 0.0.1
FROM centos:latest
MAINTAINER D.R "1304697749@qq.com"
RUN yum -y update
#RUN yum -y install 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构建的新镜像将以centos:latest操作系统为基础。在运行一个容器时,必须要指明是基于哪个基础镜像在进行构建。
  • MAINTAINER:

    MAINTAINER指令,这条指令会告诉Docker该镜像的作者是谁,以及作者的邮箱地址。这有助于表示镜像的所有者和联系方式

  • RUN:

    RUN指令会在当前镜像中运行指定的命令。这里我们通过RUN指令更新了APT仓库,安装nginx包,并创建了一个index.html文件。像前面说的那样,每条RUN指令都会创建一个新的镜像层,如果该指令执行成功,就会将此镜像层提交,之后继续执行Dockerfile中的下一个指令。默认情况下,RUN指令会在shell里使用命令包装器/bin/sh -c 来执行。如果是在一个不支持shell的平台上运行或者不希望在shell中运行(比如避免shell字符串篡改),也可以使用exec格式的RUN指令,通过一个数组的方式指定要运行的命令和传递给该命令的每个参数:

    RUN ["yum", "install", "-y", "nginx"]
  • EXPOSE:
    EXPOSE指令是告诉Docker该容器内的应用程序将会使用容器的指定端口。这并不意味着可以自动访问任意容器运行中服务的端口。出于安全的原因,Docker并不会自动打开该端口,而是需要你在使用docker run运行容器时来指定需要打开哪些端口。
    可以指定多个EXPOSE指令来向外部公开多个端口,Docker也使用EXPOSE指令来帮助将多个容器链接,在后面的学习过程中我们会接触到。

基于Dockerfile构建新的镜像

执行docker build命令的时候,Dockerfile中的所有的指令都会被执行且提交,并且在该命令成功结束后返回一新的镜像。

# cd static_web
# docker build -t="test/static_web" .

返回结果
Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon

Successfully built 94728651ce15

-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

再回到docker build过程。可以看到构建上下文已经上传到Docker守护进程:

Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon

提示:如果在构建上下文的根目录下存在以.dockerignore命名的文件的话,那么该文件内容会被按行进行分割,每一行都是一条文件过滤匹配模式。这非常像.gitignore文件,该文件用来设置哪些文件不会被上传到构建上下文中去。该文件中模式的匹配规则采用了Go语言中的filepath。

之后,可以看到Dockerfile中的每条指令会被顺序执行,而作为构建过程中最终结果,返回了新镜像的ID,即94728651ce15。构建的每一步及其对应指令都会独立运行,并且在输出最终镜像ID之前,Docker会提交每步的构建结果。

那么指令失败时会怎样?

假设我们将安装的软件包名字弄错,比如写成ngin,再次运行docker build:

# docker build -t="test/static_web" .
Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:latest
---> f5bb94a8fac4
Step 1 : MAINTAINER Bourbon Tian "bourbon@1mcloud.com"
---> Using cache
---> ce64f2e75a74
Step 2 : RUN apt-get update
---> Using cache
---> e98d2c152d1d
Step 3 : RUN apt-get install -y ngin
---> Running in 2f16c5f11250
Reading package lists...
Building dependency tree...
Reading state information...
E: Unable to locate package ngin
The command '/bin/sh -c apt-get install -y ngin' returned a non-zero code: 100

这时我们需要调试一下这次失败,我们可以通过docker run命令来基于这次构建到目前为止已经成功的最后一步创建一个容器,这里它的ID是e98d2c152d1d:

# docker run -t -i e98d2c152d1d /bin/bash
root@55aee4322f77:/# yum install -y nginx
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package ngin

再次运行出错的指令apt-get install -y ngin,发现这里没有找到ngin包,我们执行安装nginx包时,包命输错了。这时退出容器使用正确的包名修改Dockerfile文件,之后再尝试进行构建。

构建缓存:

在上面执行构建镜像的过程中,我们发现当执行yum  update时,返回Using cache。Docker会将之前的镜像层看做缓存,因为在安装nginx前并没有做其他的修改,因此Docker会将之前构建时创建的镜像当做缓存并作为新的开始点。然后,有些时候需要确保构建过程不会使用缓存。可以使用docker build 的 –no-cache标志。

# docker build --no-cache -t="test/static_web" .

构建缓存带来的一个好处就是,我们可以实现简单的Dockerfile模板(比如在Dockerfile文件顶部增加包仓库或者更新包,从而尽可能确保缓存命中)。

# cat Dockerfile

# Version: 0.0.1
FROM ubuntu:latest
MAINTAINER Bourbon Tian “bourbon@1mcloud.com”
ENV REFRESHED_AT 2017-05-18
RUN yum update
RUN yum install -y nginx
RUN echo ‘Hi, I am in your container’ > /usr/share/nginx/html/index.html
EXPOSE 80

ENV 在镜像中设置环境变量,在这里设置了一个名为REFRESHED_AT的环境变量,这个环境变量用来表明该镜像模板最后的更新时间,这样只需要修改ENV指令中的日期,这使Docker在命中ENV指令时开始重置这个缓存,并运行后续指令而无需依赖该缓存。也就是说,RUN yum update这条指令将会被再次执行,包缓存也将会被刷新为最新内容。
查看新镜像:

现在来看一下新构建的镜像,使用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;"
a4ad951b2ef91275bb918d11964d7d60889608efa3958e699030d38a681ba35e

  • -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

如果没有启动成功,则通过交互的方式进入我们新创建的镜像中,尝试启动nginx,通过分析错误日志查出不能正常启动的原因,在这里我遇到的问题是:

nginx: [emerg] socket() [::]:80 failed (97: Address family not supported by protocol)

我们需要删除/etc/nginx/sites-enabled/default 中 listen [::]:80 ipv6only=on default_server;定位到问题,我们退出容器,重新修改我们的Dockerfile:

# Version: 0.0.1
FROM ubuntu:latest
MAINTAINER D.r "1304697749@qq.com"
ENV REFRESHED_AT 2018-04-15
RUN yum update
RUN yum install -y nginx
RUN echo 'Hi, I am in your container' > /usr/share/nginx/html/index.html
RUN sed -i '22d' /etc/nginx/sites-enabled/default
EXPOSE 80

重新尝试构建我们的容器,再次启动我们新建的容器,通过docker ps -l查看是否正常启动了。

我们也可以通过docker port 来查看容器的端口映射情况:

# docker port 0b422bbcce10 80
0.0.0.0:32772
在上面的命令中我们指定了想要查看映射情况的容器ID和容器的端口号,这里是80。该命令返回了宿主机中映射的端口,即32772。

-p选项还让我们可以灵活地管理容器和宿主机之间的端口映射关系。比如,可以指定将容器中的端口映射到Docker宿主机的某一个特定的端口上:


# docker run -d -p 80:80 --name static_web test/static_web nginx -g "daemon off;"
ee09ef811a9865d9bd50c71b3ddcbd414194031f14145fdbaf339d92e3ccd1bd

上面的命令会将容器内的80端口绑定到本地宿主机的80端口上。我们也可以将端口绑定限制在特定的网络接口(即ip地址)上:

# docker run -d -p 127.0.0.1:80:80 --name static_web test/static_web nginx[:标签] -g "daemon off;"

我们也可以使用类似的方法将容器内的80端口绑定到一个特定网络接口的随机端口上:

# docker run -d -p 127.0.0.1::80 –name static_web test/static_web nginx -g “daemon off;”
Docker还提供了一个更简单的方式,即-P参数,该参数可以用来对外公开在Dockfile中的EXPOSE指令中设置的所有端口:

# docker run -d -P --name static_web test/static_web nginx -g "daemon off;"
4fd632e975ad5e47a487e5e23790124da0826886dc24b2497a561d274e4e698e

# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4fd632e975ad test/static_web “nginx -g ‘daemon of 4 seconds ago Up 3 seconds 0.0.0.0:32773->80/tcp static_web

该命令会将容器内的80端口对本地宿主机公开,并且绑定到宿主机的一个随机端口上。该命令会将用来构建该镜像的Dockerfile文件中EXPOSE指令指定的其他端口也一并公开。

# curl localhost:32773
Hi, I am in your container

到这,就完成了一个非常简单的基于Docker的Web服务器。

运行自己的docker 镜像

前面我们已经介绍了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:/#

操作过程中遇到的问题

在删除image的过程中遇到的Docker:删除images报错(Error response from daemon: conflict: unable to remove repository reference)解决方案
在Docker中,某一个正在运行的image的实例称为一个container。
一个image是一些Docker层(layer)的集合。当我们运行一个image的时候,一个对应于这个image的container就产生了。同一个image可能对应许多正在运行的container。
所以我们遇到这种问题的时候需要先将image 的所有的container进行删除,方可进行操作。
docker registry 私人仓库上传过程中遇到的问题
报错信息:

http: server gave HTTP response to HTTPS client

这个错误信息可以看出来是由于http和https协议之间的关系。
解决方案在最后面的链接里面有
:修改/etc/sysconfig/docker(这是centOS7下的1.10.3docker)文件,加上ADD_REGISTRY=’–add-registry 192.168.174.128:5000′,INSECURE_REGISTRY=’–insecure-registry 192.168.174.128:5000′

docker 的基础指令

docker images ######命令来查看所有的Docker image。
docker ps ######命令来查看所有正在运行的Docker container。
docker stop $(docker ps -q) ######来停止所有正在运行的Docker container。
docker rm -f $(docker ps -a -q) ######来停止并移除所有(正在运行的和已经停止的)Docker container
docker restart [container id] #######当一个container停止之后,如果想要重新启动这个container,可以用
docker rmi  [image id]                     ######如果要删除docker image的话,需要用
docker info                                #######查看docker 的基础信息
curl -XGEThttp://192.168.1.8:5000/v2/_catalog######获取目标仓库的镜像列表

 

######注意事项
######一般不在root权限下运行docker,这会使得docker非常不安全,一般是创建一个用户然后将用户加入到docker组里面最后切换到该用户并调用docker。

参考文档

集群的基本概念
容器的基本概念
docker基本命令
docker官方配置mirrorf
docker 的安装过程(安装过程亲测不是很准 但是是感觉最好的了)
docker入门-镜像搭建
docker 私有仓库搭建
使用http协议错误的解决方案
docker 常用命令
从registry仓库获取镜像列表

持续更新中 又不懂的也可以进行回复或者联系我的邮箱。