编译docker
git clone https://github.com/docker/docker.git
-> 制作镜像
# wget http://www.yiyun.pro/docker-dev.tar
# docker load -i docker-dev.tar
-> C/S框架, 各模块松耦合:
Docker Daemon -> Engine -> Job
在Job运行时, 使用graphdriver -> 下载Graph(镜像), networkdriver 配置网络环境, execdriver 执行用户指令; libcontainer是容器管理包, 以上方法都在其中实现;
-> 主要模块:
client, daemon, register, graph, driver, libcontainer, container
client: 对众多container管理, 通过flag命令行执行;
daemon: 接受请求后, 通过路由交给相应的Handler 处理; Http.Server -> Mux.Router -> Handler <-> engine 通过与container仓库交互, 执行Job(例如执行进程, 下载文档)
registry:存储容器镜像; search, pull, push
graph: 不仅存储镜像, 存储镜像之间的元数据, ImageID...
driver: graphdriver, networkdriver, execdriver
libcontainer: 直接访问内核中与容器相关的API, namespace、cgroups、apparmor、网络设备以及防火墙规则;
container:体现在最终的交付, 包含: rootfs文件系统, 指定的计算资源, 隔离的网络环境, 自己的Job(App);
-> pull 与 run
docker pull命令的作用为:从Docker Registry中下载指定的容器镜像,并存储在本地的Graph中,以备后续创建Docker容器时的使用;
在Handle.PostImageCreate, 将下载的image交给graphdriver; 一方创建graph对象,另一方面在GraphDB中记录image之间的关系。
docker run命令执行两部分内容, 一是,创建Docker容器所需的rootfs(layered); 二是, 创建容器的网络等运行环境,并真正运行用户指令。
PostContainersCreate: graphdriver将rootfs所有镜像,加载安装至Docker容器指定的文件目录下;
PostContainersStart: libcontainer被调用,完成Docker容器内部的运行环境初始化,并最终执行用户要求启动的命令。
1. 启动Client, 主要作用是发送comment指令;
cmd docker.go
初始化client配置flag参数, 初始化命令行参数结构体; newDaemonCommand(), cmd.Execute() 包含:PreRun(), PostRun(), PersistentPostRun() 子命令的执行顺序不同;
NewDockerCli 启动Client, command.NewDockerCli()
TlsConfig对象的创建是为了保障cli在传输数据的时候,遵循安全传输层协议(TLS)。
安全传输层协议(TLS) 用于两个通信应用程序之间保密性与数据完整性,该协议有两层组成:TLS记录协议和TLS握手协议。
2. 启动Daemon
Docker Server模块接受Docker Client的请求,并在Engine中处理请求,然后根据请求类型,创建出指定的Job并运行,运行过程的作用有以下几种可能:
向Docker Registry获取镜像,通过graphdriver执行容器镜像的本地化操作,通过networkdriver执行容器网络环境的配置,通过execdriver执行容器内部运行的执行工作等。
加载Config配置, Pidfile, Mirrors, IP(配置网络环境), Context;
type Engine struct { //创建Engine调度
handlers map[string]Handler
}
type Handler func(*Job) Status //加载Job指针, 以及其状态
3. Docker Server
Docker Server最主要的功能是:接受用户通过Docker Client发送的请求,并按照相应的路由规则实现路由分发;
-> 创建Job
job := eng.Job("serveapi", flHosts...) //flHost的作用是:配置Docker Server监听的协议与监听的地址。
"serveapi", Docker Daemon 已经Register了该key 对应的func, 执行job.Run()
-> 持续服务
最后分别创建一个goroutine来执行ListenAndServe的操作。使Docker Server监听某一指定地址,接受该地址上的请求,并对以上请求路由转发至相应的处理函数Handler处。
创建路由实例:
func NewRouter() *Router {
return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
}
m := map[string]map[string]HttpApiFunc{
"GET": {
……
"/images/{name:.*}/get": getImagesGet,
……
},
"POST": {
……
"/containers/{name:.*}/copy": postContainersCopy,
},
"DELETE": {
"/containers/{name:.*}": deleteContainers,
"/images/{name:.*}": deleteImages,
},
"OPTIONS": {
"": optionsHandler,
},
}
type HttpApiFunc func(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error
创建listener监听实例:
net.Listen(proto, addr)
创建http.Server:
httpSrv := http.Server{Addr: addr, Handler: r}
启动API服务:
httpSrv.Serve(l)
4. 什么是Docker Container
从父进程fork创建子进程后, namespace主要负责命名空间的隔离; 而cgroup主要负责子进程的资源使用的限制。
在Docker Daemon, 当用户创建并启动容器时, 进行clone与隔离;
总结Docker Container的网络,可以得出4种不同的模式:bridge桥接模式、host模式、other container模式和none模式:
-> 桥接模式:
Docker通过宿主机上的网桥(docker0)来连通容器内部的网络栈与宿主机的网络栈,实现容器与宿主机乃至外界的网络通信。
当Docker Container需要暴露服务时,内部服务必须监听容器IP和端口号port_0,以便外界主动发起访问请求。由于宿主机以外的世界,
只知道宿主机eth0的网络地址,而并不知道Docker Container的IP地址,哪怕就算知道Docker Container的IP地址,从二层网络的角度来讲,
外界也无法直接通过Docker Container的IP地址访问容器内部应用。因此,Docker使用NAT方法,将容器内部的服务监听的端口与宿主机的某一个端口port_1进行“绑定”。
-> host模式, 未作网络隔离;