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
}