docker经过3年发展,从代码量看,已经发展成为一个相对较大的项目。这里分析下docker 最初版本0.1.0时的代码,感受下一个高大上开源项目最初的样子,同时相对看后期版本代码,看最初版本代码更能明白一个项目的核心功能。
docker v0.1.0 一共才一二十个go文件。
程序入口在docker/docker.go文件的main函数。该版本的docker程序集成了客户端和服务端,根据main参数来选择客户端或者服务端角色。
func main() {
if docker.SelfPath() =="/sbin/init" {
// Running in init mode
docker.SysInit()
return
}
// FIXME: Switch d and D ? (to be more sshd like)
fl_daemon:= flag.Bool("d",false, "Daemonmode")
fl_debug:= flag.Bool("D",false, "Debugmode")
flag.Parse()
rcli.DEBUG_FLAG= *fl_debug
if *fl_daemon{
if flag.NArg() !=0 {
flag.Usage()
return
}
//服务端模式
if err :=daemon(); err != nil{
log.Fatal(err)
}
}else {
//客户端模式
if err :=runCommand(flag.Args()); err != nil {
log.Fatal(err)
}
}
}
这里主要介绍服务端模式
func daemon() error {
//实例化docker服务
service,err := docker.NewServer()
if err != nil {
return err
}
//linten端口,准备API调用
return rcli.ListenAndServe("tcp", "127.0.0.1:4242",service)
}
func NewServer() (*Server, error){
rand.Seed(time.Now().UTC().UnixNano())
if runtime.GOARCH !="amd64" {
log.Fatalf("The docker runtime currently only supports amd64(not %s). This will change in the future. Aborting.",runtime.GOARCH)
}
//准备运行时环境
runtime,err := NewRuntime()
if err != nil {
return nil,err
}
srv:= &Server{
runtime:runtime,
}
return srv, nil
}
func NewRuntime() (*Runtime, error){
return NewRuntimeFromDirectory("/var/lib/docker")
}
funcNewRuntimeFromDirectory(root string) (*Runtime, error){
//运行时环境,实际是对/var/lib/docker文件夹和元数据的管理
runtime_repo:= path.Join(root, "containers")
if err :=os.MkdirAll(runtime_repo, 0700); err != nil && !os.IsExist(err) {
return nil,err
}
//实例化镜像管理,实际就是一堆镜像文件和元数据的管理
g,err := NewGraph(path.Join(root, "graph"))
if err != nil {
return nil,err
}
//实例化tag管理
repositories,err := NewTagStore(path.Join(root, "repositories"), g)
if err != nil {
return nil,fmt.Errorf("Couldn't create Tag store:%s", err)
}
//实例化网络管理
netManager,err := newNetworkManager(networkBridgeIface)
if err != nil {
return nil,err
}
authConfig,err := auth.LoadConfig(root)
if err != nil &&authConfig == nil{
// If the auth file does not exist, keep going
return nil,err
}
runtime:= &Runtime{
root: root,
repository: runtime_repo,
containers: list.New(),
networkManager:netManager,
graph: g,
repositories: repositories,
authConfig: authConfig,
}
if err :=runtime.restore(); err != nil {
return nil,err
}
return runtime, nil
}
funcListenAndServe(proto, addr string, serviceService) error {
listener,err := net.Listen(proto, addr)
if err != nil {
return err
}
log.Printf("Listening for RCLI/%s on %s\n", proto,addr)
defer listener.Close()
for {
if conn, err :=listener.Accept(); err != nil {
return err
}else {
//匿名函数,处理API的handle协程
go func(){
if DEBUG_FLAG {
CLIENT_SOCKET= conn
}
if err :=Serve(conn, service); err != nil {
log.Printf("Error: " +err.Error() + "\n")
fmt.Fprintf(conn,"Error: "+err.Error()+"\n")
}
conn.Close()
}()
}
}
return nil
}
func Serve(connio.ReadWriter, service Service) error {
r:= bufio.NewReader(conn)
var args []string
if line, err :=r.ReadString('\n'); err != nil {
return err
}else iferr := json.Unmarshal([]byte(line), &args);err != nil{
return err
}else {
return call(service, ioutil.NopCloser(r), conn,args...)
}
return nil
}
func call(serviceService, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
return LocalCall(service, stdin, stdout, args...)
}
func LocalCall(serviceService, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
if len(args) == 0 {
args= []string{"help"}
}
flags:= flag.NewFlagSet("main",flag.ContinueOnError)
flags.SetOutput(stdout)
flags.Usage= func() { stdout.Write([]byte(service.Help())) }
if err :=flags.Parse(args); err != nil {
return err
}
cmd:= flags.Arg(0)
log.Printf("%s\n", strings.Join(append(append([]string{service.Name()}, cmd), flags.Args()[1:]...), " "))
if cmd == "" {
cmd= "help"
}
//获取方法
method:= getMethod(service, cmd)
if method != nil {
return method(stdin, stdout, flags.Args()[1:]...)
}
return errors.New("Nosuch command: " + cmd)
}
func getMethod(serviceService, name string) Cmd {
if name == "help" {
return func(stdinio.ReadCloser, stdout io.Writer, args ...string) error {
if len(args) == 0 {
stdout.Write([]byte(service.Help()))
}else {
if method :=getMethod(service, args[0]); method == nil {
return errors.New("Nosuch command: " + args[0])
}else {
method(stdin,stdout, "--help")
}
}
return nil
}
}
methodName:= "Cmd"+ strings.ToUpper(name[:1]) +strings.ToLower(name[1:])
//使用go语言的反射包来寻着相应的hand函数。在这里就是通过strings来寻找相应函数,比如API 函数是run ,那么返回的就是CmdRun方法,同时传递参数过去。
method,exists :=reflect.TypeOf(service).MethodByName(methodName)
if !exists {
return nil
}
//返回实际handle函数。
return func(stdinio.ReadCloser, stdout io.Writer, args ...string) error {
//同时传递参数过去
ret:= method.Func.CallSlice([]reflect.Value{
reflect.ValueOf(service),
reflect.ValueOf(stdin),
reflect.ValueOf(stdout),
reflect.ValueOf(args),
})[0].Interface()
ifret == nil{
return nil
}
return ret.(error)
}
}
//这里以run container 为例子
func (srv *Server) CmdRun(stdin io.ReadCloser, stdoutio.Writer, args ...string)error {
config,err := ParseRun(args)
if err != nil {
return err
}
if config.Image =="" {
return fmt.Errorf("Imagenot specified")
}
if len(config.Cmd)== 0 {
return fmt.Errorf("Commandnot specified")
}
// Create new container
container,err := srv.runtime.Create(config)
if err != nil {
return errors.New("Errorcreating container: " +err.Error())
}
if config.OpenStdin {
cmd_stdin,err := container.StdinPipe()
if err != nil {
return err
}
if !config.Detach {
Go(func() error{
_,err := io.Copy(cmd_stdin, stdin)
cmd_stdin.Close()
return err
})
}
}
// Run the container
if !config.Detach {
cmd_stderr,err := container.StderrPipe()
if err != nil {
return err
}
cmd_stdout,err := container.StdoutPipe()
if err != nil {
return err
}
if err :=container.Start(); err != nil {
return err
}
sending_stdout:= Go(func()error {
_,err := io.Copy(stdout, cmd_stdout)
return err
})
sending_stderr:= Go(func()error {
_,err := io.Copy(stdout, cmd_stderr)
return err
})
err_sending_stdout:= <-sending_stdout
err_sending_stderr:= <-sending_stderr
if err_sending_stdout !=nil {
return err_sending_stdout
}
if err_sending_stderr !=nil {
return err_sending_stderr
}
container.Wait()
}else {
if err :=container.Start(); err != nil {
return err
}
//正常创建并运行容器的话,就打印容器ID
fmt.Fprintln(stdout,container.Id)
}
return nil
}
func (runtime *Runtime) Create(config *Config)(*Container, error){
// Lookup image
img,err :=runtime.repositories.LookupImage(config.Image)
if err != nil {
return nil,err
}
container:= &Container{
// FIXME: we should generate the ID here instead ofreceiving it as an argument
Id: GenerateId(),
Created: time.Now(),
Path: config.Cmd[0],
Args: config.Cmd[1:],//FIXME: de-duplicate from config
Config: config,
Image: img.Id, //Always use the resolved image id
NetworkSettings:&NetworkSettings{},
// FIXME: do we need to store this in the container?
SysInitPath:sysInitPath,
}
container.root= runtime.containerRoot(container.Id)
// Step 1: create the container directory.
// This doubles as a barrier to avoid race conditions.
if err :=os.Mkdir(container.root, 0700); err != nil {
return nil,err
}
// Step 2: save the container json
if err :=container.ToDisk(); err != nil {
return nil,err
}
// Step 3: register the container
if err :=runtime.Register(container); err != nil {
return nil,err
}
return container, nil
}
func (container *Container) Start() error{
if err :=container.EnsureMounted(); err != nil {
return err
}
if err :=container.allocateNetwork(); err != nil {
return err
}
if err :=container.generateLXCConfig(); err != nil {
return err
}
params:= []string{
"-n", container.Id,
"-f", container.lxcConfigPath(),
"--",
"/sbin/init",
}
// Networking
params= append(params, "-g",container.network.Gateway.String())
// User
if container.Config.User != "" {
params= append(params, "-u",container.Config.User)
}
// Program
params= append(params, "--",container.Path)
params= append(params, container.Args...)
//所有的操作就是为下面的命令行准备参数和环境
container.cmd= exec.Command("/usr/bin/lxc-start", params...)
// Setup environment
container.cmd.Env= append(
[]string{
"HOME=/",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
},
container.Config.Env...,
)
var err error
if container.Config.Tty {
err= container.startPty()
}else {
err= container.start()
}
if err != nil {
return err
}
// FIXME: save state on disk *first*, then converge
// this way disk state is used as a journal, eg. we canrestore after crash etc.
container.State.setRunning(container.cmd.Process.Pid)
container.ToDisk()
go container.monitor()
return nil
}
容器需要提供一系列方法,比如mount 文件作为容器容器的rootfs。
//加载文件系统
func (container *Container) Mount() error{
image,err := container.GetImage()
if err != nil {
return err
}
return image.Mount(container.RootfsPath(),container.rwPath())
}
func (image *Image) Mount(root, rw string)error {
if mounted, err :=Mounted(root); err != nil {
return err
}else ifmounted {
return fmt.Errorf("%sis already mounted", root)
}
//获取镜像层列表
layers,err := image.layers()
if err != nil {
return err
}
// Create the target directories if they don't exist
if err :=os.Mkdir(root, 0755); err != nil && !os.IsExist(err) {
return err
}
if err :=os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
return err
}
// FIXME: @creack shouldn't we do this after going overchanges?
//加载AUFS文件系统
if err :=MountAUFS(layers, rw, root); err != nil {
return err
}
// FIXME: Create tests for deletion
// FIXME: move this part to change.go
// Retrieve the changeset from the parent and apply it tothe container
// - Retrieve thechanges
changes,err := Changes(layers, layers[0])
if err != nil {
return err
}
// Iterate on changes
for _, c := range changes {
// If there is a delete
if c.Kind ==ChangeDelete {
// Make sure the directory exists
file_path,file_name := path.Dir(c.Path),path.Base(c.Path)
if err :=os.MkdirAll(path.Join(rw, file_path), 0755);err != nil{
return err
}
// And create the whiteout (we just need to create emptyfile, discard the return)
if _, err :=os.Create(path.Join(path.Join(rw, file_path),
".wh."+path.Base(file_name)));err != nil{
return err
}
}
}
return nil
}
//获取镜像层列表
func (img *Image) layers() ([]string,error) {
var list []string
var e error
if err :=img.WalkHistory(
func(img *Image)(err error) {
if layer, err :=img.layer(); err != nil {
e= err
}else iflayer != ""{
list= append(list, layer)
}
return err
},
);err != nil{
return nil,err
}else ife != nil{ // Did an error occur inside the handler?
return nil,e
}
if len(list) == 0 {
return nil,fmt.Errorf("No layer found for image%s\n", img.Id)
}
return list, nil
}
func MountAUFS(ro []string, rw string,target string) error{
// FIXME: Now mount the layers
rwBranch:= fmt.Sprintf("%v=rw",rw)
roBranches:= ""
for _, layer :=range ro {
roBranches+= fmt.Sprintf("%v=ro:",layer)
}
branches:= fmt.Sprintf("br:%v:%v",rwBranch, roBranches)
return mount("none",target, "aufs", 0, branches)
}
func mount(source string, target string,fstype string, flags uintptr, data string) (err error) {
//最后实质就是调用系统命令mount 类型为aufs类型文件系统作为容器的rootfs
return syscall.Mount(source, target, fstype,flags, data)
}
关于镜像的管理,主要见graph.go 和image.go
type Graph struct {
Rootstring
}
//实例化图层
func NewGraph(root string) (*Graph,error) {
abspath,err := filepath.Abs(root)
if err != nil {
return nil,err
}
// Create the root directory if it doesn't exists
if err :=os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
return nil,err
}
return &Graph{
Root:abspath,
},nil
}
func (graph *Graph) Exists(id string)bool {
if _, err :=graph.Get(id); err != nil {
return false
}
return true
}
func (graph *Graph) Get(id string)(*Image, error){
// FIXME: return nil when the image doesn't exist,instead of an error
img,err := LoadImage(graph.imageRoot(id))
if err != nil {
return nil,err
}
if img.Id !=id {
return nil,fmt.Errorf("Image stored at '%s' has wrong id'%s'", id, img.Id)
}
img.graph= graph
return img, nil
}
func (graph *Graph) Create(layerData Archive, container *Container, comment string)(*Image, error){
img:= &Image{
Id: GenerateId(),
Comment:comment,
Created:time.Now(),
}
if container !=nil {
img.Parent= container.Image
img.Container= container.Id
img.ContainerConfig= *container.Config
}
if err :=graph.Register(layerData, img); err != nil {
return nil,err
}
return img, nil
}
//注册镜像
func (graph *Graph) Register(layerData Archive, img *Image) error {
if err :=ValidateId(img.Id); err != nil {
return err
}
// (This is a convenience to save time. Race conditionsare taken care of by os.Rename)
if graph.Exists(img.Id) {
return fmt.Errorf("Image%s already exists", img.Id)
}
tmp,err := graph.Mktemp(img.Id)
defer os.RemoveAll(tmp)
if err != nil {
return fmt.Errorf("Mktempfailed: %s", err)
}
if err :=StoreImage(img, layerData, tmp); err != nil {
return err
}
// Commit
if err :=os.Rename(tmp, graph.imageRoot(img.Id)); err !=nil {
return err
}
img.graph= graph
return nil
}
func (graph *Graph) Mktemp(id string)(string, error){
tmp,err := NewGraph(path.Join(graph.Root, ":tmp:"))
if err != nil {
return "",fmt.Errorf("Couldn't create temp: %s",err)
}
if tmp.Exists(id) {
return "",fmt.Errorf("Image %d already exists",id)
}
return tmp.imageRoot(id), nil
}
func (graph *Graph) Garbage() (*Graph,error) {
return NewGraph(path.Join(graph.Root, ":garbage:"))
}
//删除不是真正删除,只是暂时rename
func (graph *Graph) Delete(id string)error {
garbage,err := graph.Garbage()
if err != nil {
return err
}
return os.Rename(graph.imageRoot(id),garbage.imageRoot(id))
}
func (graph *Graph) Undelete(id string)error {
garbage,err := graph.Garbage()
if err != nil {
return err
}
return os.Rename(garbage.imageRoot(id),graph.imageRoot(id))
}
func (graph *Graph) GarbageCollect() error{
garbage,err := graph.Garbage()
if err != nil {
return err
}
return os.RemoveAll(garbage.Root)
}
func (graph *Graph) Map() (map[string]*Image, error) {
// FIXME: this should replace All()
all,err := graph.All()
if err != nil {
return nil,err
}
images:= make(map[string]*Image, len(all))
for _, image :=range all {
images[image.Id]= image
}
return images, nil
}
func (graph *Graph) All() ([]*Image,error) {
var images []*Image
err:= graph.WalkAll(func(image*Image) {
images= append(images, image)
})
return images, err
}
func (graph *Graph) WalkAll(handler func(*Image)) error {
files,err := ioutil.ReadDir(graph.Root)
if err != nil {
return err
}
for _, st := range files {
if img, err :=graph.Get(st.Name()); err != nil {
// Skip image
continue
}else ifhandler != nil{
handler(img)
}
}
return nil
}
func (graph *Graph) ByParent() (map[string][]*Image,error) {
byParent:= make(map[string][]*Image)
err:= graph.WalkAll(func(image*Image) {
image,err := graph.Get(image.Parent)
if err != nil {
return
}
if children, exists :=byParent[image.Parent]; exists {
byParent[image.Parent]= []*Image{image}
}else {
byParent[image.Parent]= append(children, image)
}
})
return byParent, err
}
func (graph *Graph) Heads() (map[string]*Image, error) {
heads:= make(map[string]*Image)
byParent,err := graph.ByParent()
if err != nil {
return nil,err
}
err= graph.WalkAll(func(image *Image) {
// If it's not in the byParent lookup table, then
// it's not a parent -> so it's a head!
if _, exists :=byParent[image.Id]; !exists {
heads[image.Id]= image
}
})
return heads, err
}
func (graph *Graph) imageRoot(id string)string {
return path.Join(graph.Root, id)
}
网络处理network.go
//最终调用iptable实现端口映射
//Wrapper around the iptables command
func iptables(args ...string) error {
if err :=exec.Command("/sbin/iptables",args...).Run(); err !=nil {
return fmt.Errorf("iptablesfailed: iptables %v", strings.Join(args, ""))
}
return nil
}
func (mapper *PortMapper) setup() error{
if err :=iptables("-t", "nat", "-N","DOCKER"); err != nil {
return errors.New("Unableto setup port networking: Failed to create DOCKER chain")
}
if err :=iptables("-t", "nat", "-A","PREROUTING", "-j", "DOCKER");err != nil{
return errors.New("Unableto setup port networking: Failed to inject docker in PREROUTING chain")
}
if err :=iptables("-t", "nat", "-A","OUTPUT", "-j","DOCKER"); err != nil {
return errors.New("Unableto setup port networking: Failed to inject docker in OUTPUT chain")
}
return nil
}
func (mapper *PortMapper) iptablesForward(rule string, port int,dest net.TCPAddr) error {
return iptables("-t","nat", rule, "DOCKER", "-p","tcp", "--dport",strconv.Itoa(port),
"-j", "DNAT","--to-destination",net.JoinHostPort(dest.IP.String(), strconv.Itoa(dest.Port)))
}
func (mapper *PortMapper) Map(port int,dest net.TCPAddr) error {
if err :=mapper.iptablesForward("-A", port,dest); err != nil{
return err
}
mapper.mapping[port]= dest
return nil
}
func (mapper *PortMapper) Unmap(port int)error {
dest,ok := mapper.mapping[port]
if !ok {
return errors.New("Portis not mapped")
}
if err :=mapper.iptablesForward("-D", port,dest); err != nil{
return err
}
delete(mapper.mapping, port)
return nil
}