Docker 架构和底层技术
架构
Docker 作用
- docker 提供了一个开发、打包和运行 application 的平台
 - 把 application 和底层 infrastructure(底层的物理设备或者虚拟设备) 隔离开来
 

底层技术
- Namespace:隔离pid、net、ipc、mnt、uts
 - Control Groups:对进程做资源限制(对于宿主机来说,一个容器就是一个进程)
 Union File System:container 和 image 的分层
Docker 镜像 & 容器
Image 镜像

文件和 Metadata 的集合(root filesystem)
- 镜像是分层的,并且每一层都可以添加、改变、删除文件,成为一个新的 Image
 - 不同的 Image 可以共享相同的分层(如 #4 和 #2 共享了相同的 Base Image)
 - Image 本身是只读的
 
bootfs:内核空间,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉
rootfs:用户空间的文件系统,包含我们熟悉的 /dev, /usr, /bin 等目录
对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。
而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。相比其他 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了(200MB左右),alpine 还不到 10MB。
获取 Image
- 通过 Dockerfile 构建
```bash
Dockerfile
-t :指定要创建的目标镜像名
. :Dockerfile 文件所在目录,可以指定 Dockerfile 的绝对路径
FROM scratch ADD hello / CMD [“/hello”] 
构建命令
docker build -t gongyz/hello-world .
- 从镜像仓库中拉取,默认从 [Docker Hub](https://cloud.docker.com) 上拉取镜像```bashdocker pull ubuntu || docker pull ubuntu:18.04 || docker pull gongyz/ubuntu:18.04
- 从已经创建的容器中更新镜像(不推荐)
# -m: 提交的描述信息# -a: 指定镜像作者# e218edb10161:容器ID# gongyz/ubuntu:version: 指定要创建的目标镜像名和Tag(默认是latest)docker commit -m="has update" -a="gongyz" e218edb10161 gongyz/ubuntu:version
基本命令
```bash查看本地镜像
docker image ls || docker images 
查找镜像
docker search name
删除本地镜像
docker rmi name/id
删除未使用的镜像
docker rmi $(docker images —filter “dangling=true” -q —no-trunc)
查看镜像的 commit 记录
docker history name/id
<a name="e41b03c7"></a>### Container 容器- 通过 Image 创建- 在 Image layer(只读层) 之上建立一个 container layer(可读写层)- Image 负责 application 的存储和分发,Container 负责运行 application<a name="e71cb1a5-1"></a>##### 基本命令```bash# 运行容器docker run id/name || docker run -it id/name || docker run --name="***" id/name# 后台运行容器docker run -d id/name# 容器的端口映射到宿主机端口docker run -d -p 5000:5000 id/name# 运行容器时指定环境变量,容器中可以通过 env 命令查看环境变量docker run -e NAME=gongyz id/name# 查看正在运行容器docker container ls || docker ps# 查看所有容器(包括已退出容器)docker container ls -a || docker ps -a# 启动/关闭容器docker start id/name || docker stop id/name# 进入某个容器并执行指定的命令docker exec -it id/name /bin/bash# 删除容器docker rm id/name# 删除所有容器docker rm $(docker ps -aq)# 删除已退出容器docker rm $(docker ps -f "status=exited" -q)# 查看容器详细信息docker inspect id/name# 查看容器运行日志docker logs id/name
Dockerfile 语法
FROM
# 指定基础镜像,尽量使用官方的 image 作为 base imageFROM scratch # 指定一个空的基础镜像FROM centosFROM ubuntu:14.04
LABEL
# 定义 Image 的 Metadata,类似于代码注释LABEL maintainer="gongyz 1018017334@qq.com"LABEL version="1.0"LABEL description="This is description"
RUN
# 执行指定的命令,每次执行 RUN 命令都会创建新的 Image Layer# 为了避免无用分层,推荐合并多条命令成一行# 为了可读性,复杂的 RUN 请用反斜杠换行# RUN 经常用于安装软件包RUN update && yum install -y vim \python-dev # 反斜杠换行
WORKDIR
# 指定工作目录# 推荐使用 WORKDIR, 不要使用 RUN cd# 尽量使用绝对目录WORKDIR /test # 如果没有会自动创建 test 目录WORKDIR demoRUN pwd # 输出结果是 /test/demo
ADD && COPY
# 将本地文件添加到镜像中指定目录# 大部分情况下,COPY 优先于 ADD# ADD还有解压的功能# 添加远程文件/目录使用 curl 或者 wgetADD hello /ADD test.tar.gz / # 添加到根目录并解压WORKDIR /rootADD hello test/ # /root/test/helloWORKDIR /rootCOPY hello test/
ENV
# 设置常量# 尽量使用 ENV 增加 Dockerfile 可维护性ENV MYSQL_VERSION 5.6 # 设置常量RUN apt install -y mysql-server="${MYSQL_VERSION}" # 引用常量
CMD
# 设置容器启动后默认执行的命令# 如果 docker run 指定了其他命令,CMD命令将会被忽略# 如果定义了多个CMD,只有最后一个会执行# DockerfileFROM centosENV name DockerCMD echo "hello $name"# 启动容器docker run image # 输出 hello Dockerdocker run -it image /bin/bash # 没有输出
ENTERPOINT
# 让容器以应用程序或者服务的形式运行# 不会被忽略# 最佳实践:写一个 shell 脚本作为 enterpoint# DockerfileCOPY docker-enterpoint.sh /usr/bin/ENTERPOINT ["docker-enterpoint.sh"]EXPOSE 27017CMD ["mongod"]
VOLUME
# 指定数据持久化的路径VOLUME /var/lib/mysql# 建议运行容器时指定 volume 的名称docker run -v mysql:/var/lib/mysql
EXPOSE
# 容器向外暴露的端口# DockerfileFROM python:2.7LABEL maintainer="gongyz 1018017334@qq.com"RUN pip install flaskCOPY app.py /app/WORKDIR /appEXPOSE 5000CMD ["python", "app.py"]# app.pyfrom flask import Flaskapp = Flask(__name__)@app.route('/')def hello():return "hello docker"if __name__ == '__main__':app.run(host="0.0.0.0", port=5000)
Dockerfile 命令格式
# shell 格式 ( shell 格式底层会调用 /bin/sh -c <command> )RUN apt-get install -y vimCMD echo 'hello docker'ENTERPOINT echo 'hello docker'# Exec 格式RUN ["apt-get", "install", "-y", "vim"]CMD ["/bin/echo", "hello docker"]ENTERPOINT ["/bin/echo", "hello docker"]
Docker 网络(重点)
单机
- Bridge Network
 - Host Network
 - Node Network
 
多机(了解)

借助于 linux 的 namespace 技术,docker 容器默认使用的是 Bridge 网络模式,该模式会为每一个容器分配、设置IP(每个容器都有自己独立的 network space),并将容器连接到一个 docker0 的虚拟网桥,通过 docker0 虚拟网桥实现容器以及容器和宿主机之间的通信。
容器虽然以及分配了IP地址,但是这个地址并不能直接访问 Internet,需要经过 NAT(网络地址转换)之后才能通过网卡连接到 Internet。
安装 Docker 时,它会自动创建三个网络,可以通过 docker network ls 命令列出这些网络:
docker network lsNETWORK ID NAME DRIVER7fca4eb8c647 bridge bridge9f904ee27bf5 none nullcf03ee007fb4 host host# 查看指定网络模式docker network inspect name/id
测试:通过 busybox 创建两个容器,测试容器之间是否能通信
# 查看本机网络情况,可以看到有一个 docker0 的虚拟网桥,且状态为DOWNip a# 创建两个容器docker run -d --name test1 busybox /bin/sh -c "while true; do sleep 3600; done"docker run -d --name test2 busybox /bin/sh -c "while true; do sleep 3600; done"# 再次查看本机网络情况,可以看到 docker0 的状态为 up,且多了两对 veth pair,这两对 veth pair 一个连接在宿主机的network space上,另一个连接在容器的 network space上ip a# 查看 Bridge Network 模式下的容器,可以看到新创建的两个容器docker network inspect bridge# 测试能否 ping 通# test1 ip 172.17.0.2# test2 ip 172.17.0.3# 宿主机 ip 192.168.240.119docker exec -it test1 ping 127.0.0.3docker exec -it test2 ping 127.0.0.2docker exec -it test1 ping 192.168.240.119docker exec -it test2 ping 192.168.240.119
容器之间的 link(很少用)
# 创建一个容器并连接到指定的容器docker network --link id/name(指定的容器)id/name(镜像)# 创建 test2 容器并 link 到 test1docker run -d --name test2 --link test1 busybox /bin/sh -c "while true; do sleep 3600; done"# ping test1ping 172.17.0.2 # ipping test1 # name
注意:test2 连接到 test1 后,可以直接通过名称访问 test1,test1 不能却直接通过名称访问 test2。
自定义 network
# 创建一个 networkdocker network create -d bridge mybridge # -d 参数指定 driver(网络模式)# 查看 network 列表docker network ls# 创建 test3 容器并指定 networkdocker run -d --name test3 --network mybridge busybox /bin/sh -c "while true; do sleep 3600; done"# 自定义网络连接到已经创建的容器docker network connect mybridge test2
注意:两个容器连接到同一个自定义 network,那么这两个容器默认是互相连接的,可以直接通过名称访问。
容器的端口映射
# 启动容器时通过 -p 参数将容器端口映射到宿主机端口docker run -d --name web -p 80:80 nginx
Host/None Network
# none network 外部无法访问该容器中启动的服务,只能进入容器中访问docker run -d --name test1 --network none busybox /bin/sh -c "while true; do sleep 3600; done"# host network 与宿主机共享一个 network space,可能存在端口冲突的问题,比如两个容器使用相同端口docker run -d --name test2 --network host busybox /bin/sh -c "while true; do sleep 3600; done"
多容器复杂应用的部署
场景: 部署一个包含 reids 数据库的 Python Web应用,web应用和 redis 在不同容器中启动
# app.pyfrom flask import Flaskfrom redis import Redisimport osimport socket# REDIS_HOST 环境变量app = Flask(__name__)redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)@app.route('/')def hello():redis.incr('hits')return 'Hello Container World! I have been seen %s times and my hostname is %s.\n' % (redis.get('hits'),socket.gethostname())if __name__ == "__main__":app.run(host="0.0.0.0", port=5000, debug=True)# DockerfileFROM python:2.7LABEL maintaner="gongyz 1018017334@qq.com"COPY . /appWORKDIR /appRUN pip install flask redisEXPOSE 5000CMD [ "python", "app.py" ]# 创建镜像docker image build -t gongyz/python-web .# 启动 redis 容器docker run -d --name redis redis# 启动 python web 容器docker run -d -p 5000:5000 --name python-web --link redis -e REDIS_HOST=redis gongyz/python-web
多机网络
前面涉及的内容都是单机网络,包括多容器应用也是部署在单机上,那不同主机之间的容器应该如何建立连接和通信呢?这就涉及到 docker 的另外一种网络模式:Overlay Network。
Docker 持久化
默认情况下,容器的数据是保存在容器的可读写层,当容器被删除时,可读层上的数据将会丢失。所以为了实现数据的持久性,需要选择一种持久化技术来保存数据。Docker 支持两种持久化方案:Data Volume 和 Bing Mouting。
Data Volume(数据卷)
- 基于本地文件系统的 Volume,可以在执行 docker create 或者 docker run 时,通过 -v 参数将主机的目录作为容器的数据卷。
 - 基于 plugin 的 Volume ,支持第三方的存储方案,比如NAS、AWS。
```bash
Docker中所有的 volume 都在宿主机上的指定目录下 /var/lib/docker/volumes
Data Volume 需要在 Dockerfile 中通过 VOLUME 参数指定数据卷
 
Dockerfile
VOLUME /var/lib/mysql
启动一个 mysql 容器(建议运行容器时指定 volume 的名称)
docker run -d -v mysql:/var/lib/mysql —name mysql -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql
<a name="jeaYm"></a>### Bind Mouting(绑定挂载)```bash# 将宿主机中已存在的目录或者文件 mount 到容器中docker run -d -p 3000:3000 -v $(pwd)/html:/node-http/html gongyz/node-http

