from flask import Flask
import socket
import os
app = Flask(__name__)
@app.route('/')
def hello():
html = "hello {name}" \
"Hostname:{hostname}
"
return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname())
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
定义依赖的文件
➜ more requirements.txt
Flask
#使用官方提供的python开发镜像作为基本镜像
FROM python:2.7-slim
#将工作目录切换到/appmul
WORKDIR /app
#将当前目录下的所有内容复制到/app下
ADD . /app
#使用pip命令安装这个应用所需要的依赖
RUN pip install --trusted-host pypi.python.org -r requirements.txt
#运行外界访问容器的80端口
EXPOSE 80
#设置环境变量
ENV NAME World
#设置容器启动进程为: python app.py
CMD ["python", "app.py"]
利用一些原语,即大写的词语,描述镜像,并且按照顺序处理
如:RUN 就是在容器里面执行shell命令的意思
最后的CMD,是指指定python app.py为这个容器的进程,等价于docker run python app.py
实际上完整的语句是ENTRYPOINT CMD,但是docker会提供一个隐含的ENTRYPOINT,即/bin/sh -c
➜ class-one tree
.
├── Dockerfile
├── app.py
└── requirements.txt
docker build -t helloworld .
-t是给镜像加一个 Tag
可以查看镜像文件
➜ class-one docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
helloworld latest e6af2f794851 11 minutes ago 148MB
docker run -p 4000:80 helloworld
-p 4000:80告诉docker,将容器内部的80端口映射到宿主机的4000端口上。
查看容器进程
➜ class-one docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ee93c027f364 helloworld "python app.py" 12 minutes ago Up 12 minutes 0.0.0.0:4000->80/tcp dazzling_kalam
➜ class-one curl http://localhost:4000
hello WorldHostname:ee93c027f364
%
➜ class-one docker inspect helloworld
...
➜ class-one docker login
➜ class-one docker tag helloworld rickey17/helloworld:v1
➜ class-one docker push rickey17/helloworld:v1
注意把rickey17换成自己的账号
docker exec -it ee93c027f364 /bin/sh
➜ class-one docker exec -it ee93c027f364 /bin/sh
# touch test.txt
# exit
➜ class-one docker commit ee93c027f364 rickey17/helloworld:v2
sha256:22a109ad4db12ed3f21007494434eab52cc83ec9018b0cbae4d7c4bf9f532253
通过exec 进入容器内部,然后新增一个文件,最后commit这个镜像,最后可可以push到仓库,这样v2版本就是以及更新过的版本啦。
docker exec是怎样做到进入容器里的呢?
实际上,Linux Namespace创建的隔离空间虽然看不到,但一个进程的Namespace信息在宿主机上是确确实实存在的,并且以文件的形式存在。
➜ class-one docker inspect --format '{{ .State.Pid}}' ee93c027f364
4572
通过这个命令可以查看容器在宿主机上的进程号,
根据进程号,可以找到该进程的所有namespace对应的文件
ls -l /proc/4572/ns
可以看到包括cgroup、ipc、mnt、net、pid、pid_for_children、user、uts对应的namespace信息。
这样我们就可以加入到一个已经存在的namespace中。
一个进程,可以选择加入到某个进程已有的namespace中,从而达到进入该进程所在容器的目的,这就是docker exec的实现原理。操作系统依赖的是一个叫setns()的方法。
但是还有一个疑问就是:
Docker Volume就是解决这样的问题,Volume机制,允许你将宿主机上指定的目录或文件,挂载到容器里面进行读取和修改操作。
docker run -v /test ...
docker run -v /home:/test ...
docker支持以上两种方式挂载
第一种,没有指定宿主机目录,会在宿主机上创建一个临时目录/var/lib/docker/volumes/[VOLUME_ID]/_data,然后挂载到容器的/test目录上;
第二种情况,将宿主机的/home目录挂载到容器的/test目录上。
在宿主机上的文件系统,自然也包括我们要使用的文件镜像,这个镜像的各个层,保存在/var/lib/docker/aufs/diff/目录下,在容器进程启动后,它们将被联合挂载到/var/lib/docker/aufs/mnt/目录中,这样rootfs就准备好啦。
在rootfs准备好之后,在chroot之前,把Volume指定的宿主机目录(/home),挂载到指定的容器目录(/test)在宿主机上对应的目录上(/var/lib/docker/aufs/mnt/[可读写层层_ID]/test)上,这个Volume挂载工作就完成了。
各重要的是,由于执行挂载操作时,“容器进程”已经创建,也就意味着mount namespace开启,所以这个挂载操作只在容器可见,在宿主机上没有这个挂载点。
容器进程:这里提到的容器进程,是docker创建的一个容器初始化进程(dockerinit),而不是应用进程(ENTRYPOINT+CMD),dockerinit负责完成根目录的准备,挂载设备和目录,配置hostname等一系列需要在容器内进行的初始化操作。最后,它通过execv()系统调用,让用户程序取代自己,成为PID=1的进程。