談到docker源碼,其實網上有很多的源碼的分析的文章,也看過一些大牛寫的docker源碼解讀的文章,收獲很大。我之前也想去看docker的源碼,但是當我把源碼下載下來一看,源碼太多了,不知道該從何處下手,看一個功能點的代碼,看完之后只知道個大概,不久就忘記的一干二凈,在加上docker現在版本更新飛速,所以就更跟不上那個腳步了。但是我又一直想看看docker的源碼,所以,我就想了個辦法,既然你版本更新太快,那我就從你docker的第一個版本看起,一看總共才20幾個go文件,頓時壓力沒有那么大了。。。
docker版本:v0.1.0
總結:
docker v0.1.0版本完全是基于lxc來實現的。
docker 啟動
先檢查docker可執行文件的絕對路徑是否在/sbin/init目錄下已經存在
如果在,則設置docker容器啟動之前的環境
做如下操作:設置網絡:添加路由,切換用戶,啟動docker程序
```
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, "Daemon mode")
fl_debug := flag.Bool("D", false, "Debug mode")
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)
}
}
}
```
如果不存在則根據參入的命令行參數:去選擇是啟動docker deamon 還是執行 docker cli 的命令調用
如果是deamon (-d)則檢查是否有多余的參數,如果有則退出,顯示幫助信息
如果沒有則啟動docker deamon,在來看看docker deamon 啟動過程,具體干了些什么事情。
看看daemon的實現:
主要是創建一個server對象, 然后通過這個server創建tcp服務端:其中最主要的是
在這個是在server的創建,創建過程比較復雜點,然后是在服務啟動tcp監聽哪里,用到了反射技術,通過把對用的docker 的cmd
命令和對應的server的方法對應上,然后通過方法名稱獲取對應的方法執行對應命令的方法,從而相應對應的tcp客戶端發送的命令
創建server的詳細過程歸納如下:創建server實質就是創建runtime對象,runtime對象中封裝了所有docker
daemon運行時所需要的所有的信息,在創建runtime時,首先會在
/var/lib/docker目錄下創建對應的文件:containers,graph文件夾,然后創建對應的鏡像tag存儲對象,通過名為lxcbr0的卡的網絡創建網絡管理,最后創建dockerhub的認證對象AuthConfig,至此server對象創建完畢。其中最復雜的就是網絡管理的創建。
下面我們來先看看一個完整的server對象的創建,到底干了些什么事情;
```
func daemon() error {
service, err := docker.NewServer()
if err != nil {
return err
}
return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service)
}
```
##server的創建
```
func NewServer() (*Server, error) {
rand.Seed(time.Now().UTC().UnixNano())
//檢查程序運行的arch是否是amd64
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中實現的docker的所有命令行中所有命令的api。
runtime, err := NewRuntime()
if err != nil {
return nil, err
}
srv := &Server{
runtime: runtime,
}
return srv, nil
}
```
對應的NewRuntime()的方法:
```
func NewRuntime() (*Runtime, error) {
return NewRuntimeFromDirectory("/var/lib/docker")
}
```
對應的NewRuntimeFromDirectory()的方法:
```
func NewRuntimeFromDirectory(root string) (*Runtime, error) {
//創建/var/lib/docker/containers文件夾
runtime_repo := path.Join(root, "containers")
if err := os.MkdirAll(runtime_repo, 0700); err != nil &&
!os.IsExist(err) {
return nil, err
}//這個判斷的意思是:創建這個文件夾,如果報錯,并且錯誤信息不是 文件見已經存在,則返回
//創建/var/lib/docker/graph目錄,同事創建Graph對象
g, err := NewGraph(path.Join(root, "graph"))
if err != nil {
return nil, err
}
/*
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
}
*/
///var/lib/docker/repositories文件夾和Graph對象創建TagStore
repositories, err := NewTagStore(path.Join(root, "repositories"), g)
if err != nil {
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
}
//通過名為lxcbr0的卡的網絡創建網絡管理
netManager, err := newNetworkManager(networkBridgeIface)
在看看網絡管理的創建,實質上通過指定名為lxcbr0的的網絡接口來實現的,一個網絡管理的實例其實包括了:網橋名字,ip網絡,ip分配器,端口分配器,端口映射器,那么在實例化一個網絡管理的時候,實質上就是要將這些屬性全部賦值,ip分配器實質上就是一個ip地址的chanle,里面的ip地址是通過lxcbr0接口的ip
和對應的網關mask計算得到的子網ip。端口分配器,實質就是一個在存放了指定范圍49153~65535個int的chanle,端口映射器實際上就是一個設置iptalbe和清楚iptable的一個方法。
/*
type NetworkManager struct {
bridgeIface? string? //網橋名字
bridgeNetwork *net.IPNet //ip網絡
ipAllocator? *IPAllocator
//ip分配器,就是我們在創建容器的時候給容器分配ip的,其實就是一個包含了網卡的ip和網關,同時有又一個ip池
portAllocator *PortAllocator //端口分配器 就是我們在創建容器的時候給容器分配端口的
portMapper? ? *PortMapper
}
func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
addr, err := getIfaceAddr(bridgeIface) //獲取指定lxcbr0網卡的ip
if err != nil {
return nil, err
}
network := addr.(*net.IPNet)
ipAllocator, err := newIPAllocator(network)
/*
type IPAllocator struct {
network *net.IPNet
queue? chan (net.IP)
}
通過網卡lxcbr0的第一個ip和網關mask 得到當前這個網卡下的所有子網ip 并且封裝成一個ip分配器(IPAllocator)
func newIPAllocator(network *net.IPNet) (*IPAllocator, error) {
alloc := &IPAllocator{
network: network,
}
if err := alloc.populate(); err != nil {
return nil, err
}
return alloc, nil
}
func (alloc *IPAllocator) populate() error {
firstIP, _ := networkRange(alloc.network)
size, err := networkSize(alloc.network.Mask)
if err != nil {
return err
}
// The queue size should be the network size - 3
// -1 for the network address, -1 for the broadcast address and
// -1 for the gateway address
alloc.queue = make(chan net.IP, size-3)
for i := int32(1); i < size-1; i++ {
ipNum, err := ipToInt(firstIP)
if err != nil {
return err
}
ip, err := intToIp(ipNum + int32(i))
if err != nil {
return err
}
// Discard the network IP (that's the host IP address)
if ip.Equal(alloc.network.IP) {
continue
}
alloc.queue <- ip
}
return nil
}
*/
if err != nil {
return nil, err
}
//創建端口分配器(實質是以一個存放49153~65535)個端口的int chanle
portAllocator, err := newPortAllocator(portRangeStart, portRangeEnd)
if err != nil {
return nil, err
}
/*
const (
networkBridgeIface = "lxcbr0"
portRangeStart? ? = 49153
portRangeEnd? ? ? = 65535
)
type PortAllocator struct {
ports chan (int)
}
func newPortAllocator(start, end int) (*PortAllocator, error) {
allocator := &PortAllocator{}
allocator.populate(start, end)
return allocator, nil
}
*/
//端口映射器通過設置iptables規則來處理將外部端口映射到容器。 它跟蹤所有映射,并能夠隨意取消映射
portMapper, err := newPortMapper()
/*
type PortMapper struct {
mapping map[int]net.TCPAddr
}
func newPortMapper() (*PortMapper, error) {
mapper := &PortMapper{}
if err := mapper.cleanup(); err != nil {
return nil, err
}
if err := mapper.setup(); err != nil {
return nil, err
}
return mapper, nil
}
func (mapper *PortMapper) cleanup() error {
// Ignore errors - This could mean the chains were never set up
iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")
iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
iptables("-t", "nat", "-F", "DOCKER")
iptables("-t", "nat", "-X", "DOCKER")
mapper.mapping = make(map[int]net.TCPAddr)
return nil
}
func (mapper *PortMapper) setup() error {
if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil {
return errors.New("Unable to setup port networking: Failed to
create DOCKER chain")
}
if err := iptables("-t", "nat", "-A", "PREROUTING", "-j",
"DOCKER"); err != nil {
return errors.New("Unable to setup port networking: Failed to
inject docker in PREROUTING chain")
}
if err := iptables("-t", "nat", "-A", "OUTPUT", "-j", "DOCKER");
err != nil {
return errors.New("Unable to setup port networking: Failed to
inject docker in OUTPUT chain")
}
return nil
}
func iptables(args ...string) error {
if err := exec.Command("/sbin/iptables", args...).Run(); err != nil
{
return fmt.Errorf("iptables failed: iptables %v",
strings.Join(args, " "))
}
return nil
}
*/
manager := &NetworkManager{
bridgeIface:? bridgeIface,
bridgeNetwork: network,
ipAllocator:? ipAllocator,
portAllocator: portAllocator,
portMapper:? ? portMapper,
}
return manager, nil
}
*/
if err != nil {
return nil, err
}
//加載/var/lib/docker/.dockercfg生成對應的auth對象
/*
type AuthConfig struct {
Username string `json:"username"`
Password string `json:"password"`
Email? ? string `json:"email"`
rootPath string `json:-`
}
*/
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
/*
//讀取/var/lib/docker/containers目錄下的所有文件夾(實際就是所有之前運行過的容器的目錄,目錄名為對應容器的id)
func (runtime *Runtime) restore() error {
dir, err := ioutil.ReadDir(runtime.repository)
if err != nil {
return err
}
for _, v := range dir {
id := v.Name()
container, err := runtime.Load(id)
if err != nil {
Debugf("Failed to load container %v: %v", id, err)
continue
}
Debugf("Loaded container %v", container.Id)
}
return nil
}
//load的實現流程如下:通過獲取對應容器id目錄下的config.json文件數據來實力話一個container對象,
func (runtime *Runtime) Load(id string) (*Container, error) {
container := &Container{root: runtime.containerRoot(id)}
if err := container.FromDisk(); err != nil {
return nil, err
}
//最后檢查config.json(實際就是對應容器的容器信息文件)是否被更改過
if container.Id != id {
return container, fmt.Errorf("Container %s is stored at %s",
container.Id, id)
}
if err := runtime.Register(container); err != nil {
return nil, err
}
return container, nil
}
//最后將加載的容器注冊到runtime中的容器list中去,具體流程如下
func (runtime *Runtime) Register(container *Container) error {
//先檢查runtime中的容器list中是否存在,存在則提示錯誤
if container.runtime != nil || runtime.Exists(container.Id) {
return fmt.Errorf("Container is already loaded")
}
//檢查容器id是否為空 如果為空則說明容器是錯誤的,則退出返回錯誤
if err := validateId(container.Id); err != nil {
return err
}
//設置容器的runtime
container.runtime = runtime
//設置容器的狀態以及容器的標準輸出,輸出,錯誤流,然后將標準輸出和錯誤寫入磁盤指定的文件中
// Setup state lock (formerly in newState()
lock := new(sync.Mutex)
container.State.stateChangeLock = lock
container.State.stateChangeCond = sync.NewCond(lock)
// Attach to stdout and stderr
container.stderr = newWriteBroadcaster()
container.stdout = newWriteBroadcaster()
// Attach to stdin
if container.Config.OpenStdin {
container.stdin, container.stdinPipe = io.Pipe()
} else {
container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently
drop stdin
}
// Setup logging of stdout and stderr to disk
if err := runtime.LogToDisk(container.stdout,
container.logPath("stdout")); err != nil {
return err
}
if err := runtime.LogToDisk(container.stderr,
container.logPath("stderr")); err != nil {
return err
}
// done
//將container 加入到runtime中的容器list的最后
runtime.containers.PushBack(container)
return nil
}
*/
```
##創建tcp服務端:
創建一個監聽 接受tcp的請求,為每個請求開啟一個單獨的攜程處理請求 ,如果有請求到來則進行處理
```
func ListenAndServe(proto, addr string, service Service) 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 {
//接受tcp的請求
if conn, err := listener.Accept(); err != nil {
return err
} else {
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
}
//獲取請求中的參數然后調用call,call的一系列調度過程如下
func Serve(conn io.ReadWriter, service Service) error {
r := bufio.NewReader(conn)
var args []string
if line, err := r.ReadString('\n'); err != nil {
return err
} else if err := json.Unmarshal([]byte(line), &args); err != nil {
return err
} else {
return call(service, ioutil.NopCloser(r), conn, args...)
}
return nil
}
func call(service Service, stdin io.ReadCloser, stdout io.Writer, args
...string) error {
return LocalCall(service, stdin, stdout, args...)
}
```
根據參數是否有值來執行不同方法,如果沒有參數,則執行runtime的help方法,也就是我們通常輸入docker
這個命令看到的那些heilp的信息,如果有參數,在進行參數的處理,處理邏輯:獲取第二個參數,就是docker
后的命令,然后獲取命令之后的所有參數,進行整條命令的打印日志輸出,之后再通過cmd命令和反射技術去找到對應的cmd所對應的方法,最后找到方法將參數傳入方法,執行cmd對應的方法,結構返回connect中。至此整個deamon啟動,到處理具體的api請求全部完成
```
func LocalCall(service Service, 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("No such command: " + cmd)
}
func getMethod(service Service, name string) Cmd {
if name == "help" {
return func(stdin io.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("No such command: " + args[0])
} else {
method(stdin, stdout, "--help")
}
}
return nil
}
}
methodName := "Cmd" + strings.ToUpper(name[:1]) +
strings.ToLower(name[1:])
method, exists := reflect.TypeOf(service).MethodByName(methodName)
if !exists {
return nil
}
return func(stdin io.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()
if ret == nil {
return nil
}
return ret.(error)
}
}
*/
}
```
**最后做個總結**:
docker
daemon是運行過程如下:首先在添加網橋的默認路由,切換用戶權限等操作,然后再在/var/lib/docker目錄下創建containers,graph等文件夾(如果已經存在則將containers中的所有容器加載到runtime(我認為runtime實際就是docker-deamon的運行時環境)運行時環境中),然后創建image的tag存儲,然后創建對應的網絡管理器,最后加載認證文件,最后啟動tpc服務監聽處理請求。