Docker:快速上路
一. 简介
Docker核心是由Linux Namespace 的隔离能力、Linux Cgroups 的限制能力,以及基于 rootfs 的文件系统三个组成,
枯燥的指令集毫无乐趣,所以我借鉴了他人的教学方法,采用一个小demo从头到尾运行一边docker镜像的全生命流程。
二. 构建流程
我们今天采用docker进行一个Flask Web项目的全流程构建,部署和推送等。
相关代码在此链接中:flask_web项目地址
2.1 构建App
这个项目将启动一个Flask项目,暴露一个18080的端口,当访问Web根目录时,将会返回向一个'Hello World!' + hostname
的字符串,hostname代表当前容器的hostname。
app.py
文件如下:
from flask import Flask
import socket
app = Flask(__name__)
@app.route('/')
def hello_world():
hostname = socket.gethostname()
return 'Hello World!' + hostname
if __name__ == '__main__':
app.run(host='0.0.0.0', port=18080)
2.2 编写Dockerfile
Dockerfile 的设计思想,是使用一些标准的原语,描述我们所要构建的 Docker 镜像。并且这些原语,都是按顺序处理的,这也算是一种应用的自描述
,是一种很符合DevOps的要求。
关于编写Dockerfile的源码也已将放到GitHub上面了,内容如下:
# 拉取Python3
FROM rackspacedot/python37
# 将工作目录切换为/app
WORKDIR /app
# 将当前目录下的所有内容复制到/app下
ADD . /app
# 使用pip命令安装这个应用所需要的依赖
RUN pip install --trusted-host pypi.python.org -r requirements.txt
# 允许外界访问容器的18080端口
EXPOSE 18080
# 设置容器进程为:python app.py,即:这个Python应用的启动命令
CMD ["python", "app.py"]
关于docker file里面的原语内容可以如下理解:
- FROM:指docker从rackspacedot拉取一个python37的基础镜像
- WORKDIR:切换容器的工作目录到/app目录下
- ADD:理解为复制dockerfile所在目录的内容到/app工作目录
- RUN:容器里执行 shell 命令
- EXPOSE:声明容器暴露一个端口18080
- CMD:指定 python app.py 为这个容器的进程,app.py 的实际路径是 /app/app.py。
- ENTRYPOINT(默认):实际进程是:/bin/sh -c "python app.py",即 CMD 的内容就是 ENTRYPOINT 的参数,所以Docker 容器的启动进程为 ENTRYPOINT,而不是 CMD。
2.3 docker build
docker build 会自动加载当前目录下的 Dockerfile 文件,然后按照顺序,执行文件中的原语。
在编写Dockerfile所在的目录,执行如下的指令:
docker build -t flask_web .
关于指令细节:
- -t:给这个镜像加一个 Tag
- flask_web:这个镜像的名称
Dockerfile 中的每个原语执行后,都会生成一个对应的镜像层。即使原语本身并没有明显地修改文件的操作(比如,ENV 原语),它对应的层也会存在。只不过在外界看来,这个层是空的。
当我们构建成功后,可以使用如下指令查看本地镜像内容:
docker image ls
docker image list
不过,上图中构建出的镜像非常大,这说明我选择的原始镜像非常有问题,这也是后续优化的点,这章不做讨论。
2.4 docker run
构建完的镜像都存在于本地,所以我们可以非常简单的使用docker run
指令来运行。
指令如下:
docker run -p 18090:18080 flask_web
关于指令细节,作用如下:
- p:指定映射端口,将宿主机18090端口映射到容器的18080端口
- flask_web:为本地flask_web的镜像名称
- 容器启动之后,我可以使用
docker ps
命令看到:
docker ps
ps
- 我们需要访问该web,可以浏览器访问:
http://localhost:18090/
我们将看到Hello World!2d278afce7a7
的内容,代表我们容器的成功运行。
2.5 docker push
当我们完成镜像的编写,我们可以把这个容器的镜像上传到 DockerHub 上分享给更多的人。
- 首先需要注册一个 Docker Hub 账号,然后使用
docker login
命令登录 -
docker tag
给容器镜像取一个完整的名字
docker tag flask_web wy1140174371/flask_web:v1.0.0
具体指令作用如下:
- flask_web:代表tag的镜像名称
- wy1140174371:即为个人的dockerhub账户,也叫‘repository’
- /flask_web:代表当前项目的名称
- v1.0.0:代表tag的版本,小步增量
- 采用
docker push
推送到docker hub上
执行如下指令:
docker push wy1140174371/flask_web:v1.0.0
上传成功后,可以在此处查看docker hub的镜像内容,flask_web
2.6 docker commit(optional)
docker commit
把一个正在运行的容器,直接提交为一个镜像。一般来说,需要这么操作原因是:当这个容器运行起来后,我又在里面做了一些操作,并且要把操作结果保存到镜像里。例如我在容器内新增了一个文件。
具体指令可以使用如下:
docker commit container-id wy1140174371/flask_web:v1.0.1
具体参数含义如下:
- container-id:此处需要自己替换为正在运行的容器id,通过
docker ps
获取。 - v1.0.1:版本注意增量
三. Volume
Volume 机制,允许我们将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作.
3.1 俩种方式
在 Docker 项目里,它支持两种 Volume 声明方式,可以把宿主机目录挂载进容器的 /test 目录当中:
docker run -v /test ...
docker run -v /home:/test ...
当然俩种方式有一定的区别:
- 不声明式
由于没有显示声明宿主机目录,那么 Docker 就会默认在宿主机上创建一个临时目录/var/lib/docker/volumes/[VOLUME_ID]/_data
,然后把它挂载到容器的 /test 目录上。 - 指定宿主机目录
这种情况下,Docker 就直接把宿主机的 /home 目录挂载到容器的 /test 目录上。
3.2 原理
这个功能利用了Linux 的绑定挂载(bind mount)
机制。它的主要作用就是,允许我们将一个目录或者文件,而不是整个设备,挂载到一个指定的目录上。并且,这时我们在该挂载点上进行的任何操作,只是发生在被挂载的目录或者文件上,而原挂载点的内容则会被隐藏起来且不受影响。
3.3 图解
图解如下,mount --bind /home /test
,会将 /home
挂载到 /test
上。其实相当于将 /test
的 dentry
,重定向到了 /home
的 inode
。这样当我们修改 /test
目录时,实际修改的是 /home
目录的 inode
。这也就是为何,一旦执行 umount
命令,/test
目录原先的内容就会恢复:因为修改真正发生在的,是 /home
目录里。
四. 拓展
4.1 docker exec
docker exec
指令可以让我们进入容器内,并在容器内执行相关操作。
如下例子,就是一个进入容器后,并运行/bin/sh
.
docker exec -it container-id /bin/sh
4.2 docker inspect
docker inspect
指令可以看到当前正在运行的 Docker 容器的进程号(PID),即宿主机上进程号。
具体如下:
docker inspect --format '{{ .State.Pid }}' container-id
4.3 ENTRYPOINT
Docker 容器的启动进程为 ENTRYPOINT,而不是 CMD,即 CMD 的内容就是 ENTRYPOINT 的参数。
4.4 Docker Registry
DockerHub只是官方的镜像上传系统,但是我们也是可以自建其他的镜像系统的,例如Harbor。就比如我们的Java领域的Maven仓库,除了maven,github,sonatype等外,也可以自建nexus仓库等,按照实际需求来即可。
五. 总结
关于docker已经使用很多年了,在最近的时间内重新巩固了一下基础知识。这些东西其实光看光听帮助不大,主要的是我们需要通过实操来巩固记忆。
学习金字塔模型底层的主动式学习是最有效的方式,我一直是一个推崇方法论的实践者。
我经过了很久的工作秘密,确定了走DevOps的方向,在此路上我还是一个新手,大家共勉。
欢迎关注我的博客:https://blog.wyatt.plus
Reference
https://time.geekbang.org/column/article/18119?utm_campaign=guanwang&utm_source=baidu-ad&utm_medium=ppzq-pc&utm_content=title&utm_term=baidu-ad-ppzq-title
http://docker-saigon.github.io/post/Docker-Internals/#how:cb6baf67dddd3a71c07abfd705dc7d4b
https://medium.com/@kasunmaduraeng/docker-namespace-and-cgroups-dece27c209c7