目录
一、Docker的基本概念
1、容器与虚拟机
2、什么是Docker
3、镜像、容器、仓库
二、Docker的应用场景
1、Docker的作用
2、Docker的局限
三、Docker安装和使用
1、Windows系统Docker安装
2、Linux系统Docker安装
3、Docker常用命令
四、Docker部署python服务实战
1、python程序
2、拉取基础镜像
3、创建应用的镜像
4、上传镜像
5、运行容器
6、测试容器
7、建立本地仓库
五、 总结与反思
前言:
对Docker的研究,一方面来自于领导安排的研究任务,另一方面也来自于开发部署应用时遇到的实际问题,所以对于这个能解决现实痛点的工具产生了研究兴趣。
应用部署可以说是软件开发过程中最大的麻烦事:生产环境权限的限制、操作系统之间的差异、运行环境的配置、依赖库和组件的安装,只有这些都解决了,应用程序才能在这台机器上运行。
例如我要部署一个Python程序:
如果甲方客户权限管理严格,不提供服务器的root权限,整个过程更要麻烦好几倍。
Dock是码头,Docker的直译是码头工人。Docker将交付运行环境想象成海运,操作系统如同一个货轮,Docker这个码头工人将应用及其运行环境打包成一个个标准的集装箱,集装箱之间不会相互影响,使用者可以用标准化手段自由组装运行环境来最终交付。
如今,Docker已经成为Linux最流行的应用部署工具,在很多公司被规定为标准的应用部署方案。另外在Java等开发工程师的招聘面试中,熟悉Docker也成为了重要的加分项。
在了解Docker是什么之前,首先需要理解这两个有区别的概念:容器和虚拟机。
虚拟机,对于程序员应该都比较熟悉,比如我们常用的VMware或VirtualBox。虚拟机包含一个完整的操作系统,并模拟一整台机器的硬件,我们知道建立虚拟机的时候要设置CPU核心数、内存大小、硬盘大小等,虚拟机一旦开启,这些分配的资源就被全部占用了。
容器,也是实现虚拟化的一种方式。不同的是,它不需要模拟一个完整的操作系统,更不需要模拟硬件资源,而是将应用程序、依赖关系及其运行环境打包组成一个构建块,使之可以在隔离的进程中运行,容器之间使用沙箱机制隔离,相互之间没有接口。同时,容器和宿主机及其他容器共享硬件资源和操作系统,这样,服务器操作系统对应用只需要承载就可以了,而不需要对操作系统环境进行过多改造。
总体来说,容器的应用范围更广,一个容器包含的东西,小到一个HelloWorld程序,大到一个完整的操作系统,所以容器可以覆盖大部分虚拟机的功能。容器不但可以在物理机上运行,同样可以在虚拟机上运行,现在流行的云计算解放了企业对硬件的管理,容器解放了对应用的部署和管理,所以云计算+容器的组合已经成为当下的一种潮流。
两者的区别如下图:
Docker 是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源。
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,是目前最流行的Linux容器解决方案。
Docker 的命令和使用很简单,用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。
这是Docker的三个基本概念:
简单表述三者的关系,容器是镜像的实例,而仓库是一类镜像的统称。从面向对象思想理解,可以认为镜像是一个类,容器是它的实例化对象,而仓库是一组有相同名称不同版本的类的总称。
Image (镜像):
Docker 镜像可以理解为一个特殊的文件系统,除了包含容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如数据卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后不会被改变。
镜像内部是分层实现的,这一点在拉取镜像时会有所体现,下载的不是单一一个文件,而是多个文件,称为只读层,在逻辑上重叠在一起,并且上一层有指针指向下一层。镜像提供了这个文件系统的统一视角,对使用者隐藏了多层的存在。参见下图:
Container (容器):
容器就是将镜像实例化运行起来的状态,是动态可变化的,可以被暂停、终止、重启,也可以将变化固态化成为一个新的镜像。在实现细节上就是在镜像的只读层上面加了一个读写层,参见下图:
Repository (仓库):
这里注意不要把仓库 (Repository) 和注册服务器 (Registry) 混为一谈。注册服务器是一个集中的存储、分发镜像的服务;仓库则是一组相同名称不同标签(版本)的镜像的统称,另外仓库在注册服务器中还可以分组存储。
Docker中,一个镜像具体表示为:<仓库名>:<标签>
这里对Docker的架构原理和进程结构不做阐述,有兴趣的可以自行研究。
没有一种工具是万能的,在合适的应用场景充分发挥他的长处才是正确的做法。Docker同样有其应用场景及优势劣势。
Docker官网上给出的典型应用场景:
总结起来就一句话:使应用及服务的部署和管理更加方便。
简化配置:这是Docker公司宣传的主要使用场景。虚拟机的最大好处是能在你的硬件设施上运行各种配置不一样的平台(软件、系统),而Docker在降低了额外开销的情况下提供了同样的功能。它能让你将运行环境和配置放在容器中然后部署,使同一个容器的配置可以在不同的环境中使用。
代码流水线管理:代码从开发者的机器到最终在生产环境上的部署,可能需要经过很多个中间环境。而每一个中间环境都有或大或小的差别,Docker为应用提供了一个从开发到上线均一致的环境,简化了代码的流水线。
提高开发效率:显而易见,容器免除了开发人员对不同环境繁琐的配置和排错,而且可以帮助开发人员快速的搭建开发环境,而且不必努力使开发环境贴近生产环境。
隔离应用:一个服务器上可能存在多个不同的应用,所依赖的运行环境不同。比如java程序有很老的应用依赖jdk1.0,新的应用依赖jdk1.8,又比如python程序开发自不同版本的解释器和依赖库,docker可以将应用和环境一起隔离,而不担心相互影响。
整合服务器:docker隔离应用的能力,和共享硬件资源,有效提高了服务器的利用率,所以可以通过减少服务器的使用来降低成本。
调试能力:Docker提供了很多功能,包括可以为容器设置检查点、设置版本和查看两个容器之间的差别,这些特性可以帮助调试Bug。
多租户:对于同一个应用的镜像,可以通过运行不同的容器来给不同的用户使用,这个过程极其简单,对于镜像只不过是多实例化几个容器,容器运行起来又是相互隔离的。这得益于容器的启动速度快和高效的diff命令。
快速部署:在没有虚拟机之前,购买一个物理机到安装配置可能需要几天时间,虚拟机将这个时间减少到分钟级别,而创建一个容器再次将这个过程减少到秒级。
隔离性:与虚拟机相比,docker隔离性更弱,docker属于进程之间的隔离,虚拟机可实现系统级别隔离。
安全性: docker 的安全性也更弱。 Docker 的租户 root 和宿主机 root 等同,一旦容器内的用户从普通用户权限提升为root权限,它就直接具备了宿主机的root权限,进而可进行无限制的操作。虚拟机租户 root 权限和宿主机的 root 虚拟机权限是分离的,并且虚拟机利用如 Intel 的 VT-d 和 VT-x 的硬件隔离技术,这种隔离技术可以防止虚拟机突破和彼此交互,而容器至今还没有任何形式的硬件隔离,这使得容器容易受到攻击。
可管理性:docker 的集中化管理工具还不算成熟。各种虚拟化技术都有成熟的管理工具,例如 VMware 提供完备的虚拟机管理能力。
系统支持性:Docker是基于Linux 64bit的,对windows7的支持实际也是通过虚拟机实现的。而且Linux也不是所有版本都支持的,比如CentOS只能支持7以上版本。
网络管理:实现方式比较简单,主要通过命名空间来隔离。不如虚拟机有多种完善的网络管理方式。
一般开发人员的应用开发环境是Windows,生产环境是Linux,所以介绍这两种系统的Docker安装。
Windows 7需要利用 docker toolbox 来安装。
下载地址:
官网下载:https://download.docker.com/win/stable/DockerToolbox.exe
阿里云镜像下载:http://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/
Win7的安装很简单,按提示安装即可:
安装完成后,点击 Docker QuickStart 图标来启动 Docker Toolbox 终端。
长这样:
安装系统版本:
CentOS Linux release 7.7.1908 (Core)
1、最好先更新一下yum程序
[root@centos7-pure ~]# yum update -y
2、查看docker已安装旧版本,如果有则remove卸载
[root@centos7-pure ~]# yum list installed | grep docker
3、安装依赖软件
[root@centos7-pure ~]# yum install -y yum-utils device-mapper-persistent-data lvm2
4、添加docker的yum源
[root@centos7-pure ~]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
5、安装docker社区版
[root@centos7-pure ~]# yum install docker-ce
6、启动服务并加入开机自启动
[root@centos7-pure ~]# systemctl start docker
[root@centos7-pure ~]# systemctl enable docker
7、验证安装成功
[root@centos7-pure ~]# docker version
下面从程序员熟悉的Hello World入门开始:
1、搜索镜像
[root@centos7-pure ~]# docker search hello-world
搜索默认是官方的公有仓库:
Docker Hub地址:https://hub.docker.com/
建立自己的容器,首先需要获取一个原始镜像。所以需要从公有仓库选择一个合适的基础镜像来创建自己的容器。可以search命令搜索,也可以登录Docker Hub浏览。
2、拉取镜像
[root@centos7-pure ~]# docker pull hello-world
对于镜像,如果不指定tag,就会使用默认标签:latest。
3、查看镜像
[root@centos7-pure ~]# docker images
4、运行镜像
run实际是根据镜像创建容器,并运行容器。
[root@centos7-pure ~]# docker run hello-world
这个容器运行就是输出这段提示,然后容器进入stop状态,因为这个容器是一个程序,如果是提供服务的容器,运行后就是开始提供服务,而不会自动终止。
5、查看容器
正在运行的容器:docker ps
所有容器:docker ps –a
Container_id和names都可以唯一确定一个容器,容器名可以在创建容器时指定,上面命令没有指定容器名称,所以是随机生成的名字。
6、重新运行容器
这时我们想再运行一下这个容器,就不能用docker run 镜像了,因为这个命令会再创建一个新的容器出来。可以使用容器命令start。
[root@centos7-pure ~]# docker start -i 1d1f18e532a1
使用Container_id或names都可以指定容器,-i是容器可交互,否则默认只会返回一个Container_id。
从以上操作可以看出,Docker的命令都很规范。
命令格式都是:docker 命令 [可选项] 对象 [参数...]
命令帮助可以查看docker –help 或 docker COMMAND –help。
在明白了docker的组件关系和命令规范后,再看这张命令图表就很清晰了,结合命令帮助,基本就可以自己写docker命令了。
其中,Tar files是镜像在不同机器上迁移的一种方式,即通过压缩文件的形式对docker保存或加载镜像;Dockerfile是创建镜像的主要方式。
这里通过一个简单的python的web服务,使用Docker完成一个从打包服务、分发镜像到容器部署的完整过程。
尽量模拟完整的使用场景,包括使用第三方依赖包、读本地文件、提供http服务、写本地文件和记录程序日志等。
server.py:
from flask import Flask, make_response
app = Flask(__name__)
@app.route('/', methods=['GET'])
def hello_world():
file = 'volume/hello.txt'
out = 'volume/out.txt'
with open(file, 'r') as f:
txt = f.read()
print(txt)
with open(out, 'w') as f:
f.write(txt)
response = make_response(txt)
return response
if __name__ == '__main__':
app.run('0.0.0.0', port=5000, debug=False, threaded=True)
代码很简单:引入依赖库Flask,提供一个http服务,当请求5000端口时,程序读取主机中hello.txt的内容”Hello World!”并返回网页,且打印到日志,同时将内容写到一个新的文件out.txt中。
生成依赖文档:
Python通过工具生成一个requirements.txt,里面包含项目的依赖库,本例只有一条:Flask==1.1.1
创建python服务的镜像,首先要有一个python环境的基础镜像。基础镜像一般选择与开发环境相同版本,拉取一次就可以反复使用了。我们去https://hub.docker.com/的python仓库查找一个合适的镜像。
我的python版本是3.6.4,选择同样版本,而且看3.6.4-alpine是其中最小的,所以选择这个镜像。
拉取到开发环境:
这个镜像可以创建一个python容器,其功能与安装的python环境没有区别,可以交互、可以解释执行py脚本等,这里不做展开。我们需要的是把它作为python程序的运行环境,以这个镜像为基础,包装出具体的服务镜像。
Docker采用Dockerfile的方式创建镜像,Dockerfile是一个文本文件,其中记录镜像的内容和创建步骤。
在程序目录创建文件Dockerfile,注意没有后缀名。
本例的Dockerfile:
FROM python:3.6.4-alpine
MAINTAINER Daniel
COPY . /app
WORKDIR /app
RUN mkdir -p /app/volume
RUN pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
EXPOSE 5000
ENTRYPOINT ["/bin/sh", "-c"]
CMD ["python -u server.py >> volume/test_service.log 2>&1"]
Dockerfile步骤解析:
1)以镜像python:3.6.4-alpine作为基础镜像,如果本地没有这个镜像,会自动从Docker Hub拉取。
2)设置维护者的信息。
3)复制Dockerfile所在目录的所有文件,包括server.py和requirements.txt,到容器内的虚拟目录/app中。
4)指定容器的默认工作目录为/app。
RUN指令在创建镜像时执行,相当于给基础镜像增加了一个改动层,然后固化为新的只读层,添加到新的镜像中。
5)创建文件夹/app/volume准备作为数据卷,即宿主机目录挂载的位置。
6)升级pip工具,为安装依赖包做准备(使用清华的python库镜像,提高速度)。
7)从requirements.txt安装python的第三方依赖包。
8)暴露容器的5000端口,因为本例是一个web服务,需要暴露端口。
9) ENTRYPOINT给出程序入口命令,使容器可以表现的像一个可执行程序,本例使用sh –c作为默认程序。本来也可以使用python命令作为入口,但是容器内想使用重定位符获得输出日志,必须在sh –c “CMD”中使用重定位。这里踩坑无数,docker的容器日志用起来又太麻烦。
可以有两种形式:
ENTRYPOINT ["executable", "param1", "param2"] :推荐使用的 exec形式
ENTRYPOINT command param1 param2 :shell 形式
10)CMD给出容器启动时默认执行的命令,如果有ENTRYPOINT,则为ENTRYPOINT提供参数;如果没有,则是完整的默认命令。有三种形式:
CMD ["executable","param1","param2"]:推荐使用的 exec 形式
CMD ["param1","param2"]:无可执行程序形式
CMD command param1 param2:shell 形式
本例是执行server.py脚本,并将标准输出和错误输出重定向到主机挂载目录的test_service.log。
Dockerfile每一行以一个大写指令开头,主要分为四个部分:
基础镜像信息指令 FROM
维护者信息指令 MAINTAINER
镜像操作指令 RUN、EVN、ADD和WORKDIR等
容器启动指令 CMD、ENTRYPOINT和USER等
然后从Dockerfile建立服务的镜像:
进入程序目录,建立镜像:
-t是指定新的镜像名,后面接<仓库名>:<标签>。注意最后有个点,表示从当前目录的Dockerfile建立镜像。
查看镜像列表,发现应用的镜像已生成:
向服务器上传镜像有两种方式:一种是保存为Tar文件上传,再load为镜像;另一种是在服务器建立本地私有仓库,直接push进本地仓库,再分发。
这里使用文件上传方式。
将镜像test_service:1.1.0保存为文件test_service.tar
服务器建立程序目录,并上传文件test_service.tar到该目录。
[root@centos7-pure ~]# mkdir -p ~/python/test_service
[root@centos7-pure ~]# cd ~/python/test_service
从Tar文件导入为镜像:
[root@centos7-pure test_service]# docker load -i test_service.tar
查看镜像已导入:
我们希望服务在后台运行,使用主机的8333端口提供服务,能操作程序目录中的内容,并且如果异常终止可以自动重启:
[root@centos7-pure test_service]# docker run -d -p 8333:5000 -v /root/python/test_service:/app/volume --name docker_test_service --restart always test_service:1.1.0
-d:让容器在后台运行。
-p:将主机的端口映射为容器的端口(前面是宿主机端口,后面是容器端口)
-v:挂载主机的目录到容器的目录,相当于在容器内建立了宿主机某个目录的软连接,使得容器可以读写主机目录。注意:容器中用来挂载的目录一定要是空目录,不然目录中原有的文件就不能读写了,之前对数据卷的意义不清晰,使用了/app工作目录挂载了宿主机目录,结果被折磨的欲仙欲死。
--name:为容器命名。
--restart:always表示容器如果意外终止会自动重启,保证服务可用性。
最后是镜像名,标签是lastest的可以省略标签。
查看运行的镜像:
进入程序目录,发现容器启动后开始记录日志文件test_service.log。
创建程序需要读取的hello.txt文件,添加内容“Hello World!”。
使用浏览器发送请求:
可以看到网页返回预期的结果。
继续查看out.txt写入了内容,日志文件也记录了完整的日志。整个过程如下图:
容器完美运行!
前面整个过程是通过打包文件的方式上传服务器,简单方便而且适用网络隔离的情况,但是大量部署的效率比较低,而且不能集中管理项目的应用镜像。
建立本地仓库是对项目中镜像进行集中版本管理的基础,就像项目管理首先要建立SVN或Git Hub一样。所以我们需要在服务器建立一个类似Docker Hub的本地仓库,来管理自有的docker镜像。
我们需要下载安装一个Docker Registry软件,这里变得有意思了,既然我们有Docker了,那么一切都可以是容器。我们直接拉取Registry的镜像,然后运行容器就可以提供Docker注册服务器的本地仓库服务了。
拉取Registry镜像:
[root@centos7-pure test_service]# docker pull registry
建立本地仓库存储目录:
[root@centos7-pure test_service]# mkdir -p /opt/data/registry
运行容器:
[root@centos7-pure test_service]# docker run -d -p 5000:5000 -v /opt/data/registry:/var/lib/registry --name hl_registry registry:latest
查看运行的容器:
接下来就可以从本机的开发环境直接将创建好的应用镜像上传到这个本地仓库中。
首先需要将镜像起一个别名以匹配本地仓库的格式:这时会发现镜像多了一个,但是和原来的镜像IMAGE ID相同:
上传镜像到本地仓库:
Win7 docker报错,上传没有成功,linux也存在同样问题:这是因为docker客户端默认发送的是https请求,而搭建的本地仓库只支持http请求。需要搭建https服务,后续继续研究。
不过我们已经通过Tar文件上传的方式已经将镜像提交到了本地仓库所在的服务器,通过本地地址上传可以成功。
[root@centos7-pure test_service]# docker tag test_service:1.1.0 127.0.0.1:5000/test_service:1.1.0
[root@centos7-pure test_service]# docker push 127.0.0.1:5000/test_service:1.1.0
通过Registry的API接口可以查看服务器中的docker仓库及其版本。
1、Docker的确方便了应用的部署和版本管理,但是如何对容器资源和运行情况监控值得思考。
2、本地仓库的搭建和使用需要继续研究。
3、使用Compose定义和运行多容器的应用,由多个容器协作部署复杂的运行环境,需要继续研究。
4、Docker的安全性和管理工具需要继续改进。