標籤:

docker源碼學習-docker run 的具體實現(2)

轉載至docker源碼學習-docker run 的具體實現(2 )

在第一博文中看的Docker deamon的啟動過程是如何實現的,這篇博文主要看的是docker run 這個命令啟一個容器是如何實現的。docker大多數的命令對應的處理方法都在runtime .Go這個文件中。但是run方法的實現在Container.go文件中,接下來我們來看看這個run這個方法是如何實現的。

func (container *Container) Run() error {n if err := container.Start(); err != nil {n return errn }n container.Wait()n return niln}n

func (container *Container) Start() error {n if err := container.EnsureMounted(); err != nil {n return errn }n if err := container.allocateNetwork(); err != nil {n return errn }n if err := container.generateLXCConfig(); err != nil {n return errn }n params := []string{n "-n", container.Id,n "-f", container.lxcConfigPath(),n "--",n "/sbin/init",n }nn // Networkingn params = append(params, "-g", container.network.Gateway.String())nn // Usern if container.Config.User != "" {n params = append(params, "-u", container.Config.User)n }nn // Programn params = append(params, "--", container.Path)n params = append(params, container.Args...)nn container.cmd = exec.Command("/usr/bin/lxc-start", params...)nn // Setup environmentn container.cmd.Env = append(n []string{n "HOME=/",n "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",n },n container.Config.Env...,n )nn var err errorn if container.Config.Tty {n err = container.startPty()n } else {n err = container.start()n }n if err != nil {n return errn }n // FIXME: save state on disk *first*, then convergen // this way disk state is used as a journal, eg. we can restore after crash etc.n container.State.setRunning(container.cmd.Process.Pid)n container.ToDisk()n go container.monitor()n return niln}n

start()方法中第一步先檢查是否有mount的目錄掛在,如果有則進行掛載,如果已經掛載過,則不操作,返回,如果沒有掛載過,則進行相應的掛載。然後是分配容器的網路,之後根據lxc的配置文件模板生成lxc啟動的配置文件。然後組裝lxc啟動的命令行參數,並且指定容器的cmd為lxc的啟動命令,在指定lxc經常的環境,判斷是否需要將容器的輸出,輸出到標準輸出終端tty,最後啟動容器,如果沒有錯誤,則改變內存中container(實際上是runtime中的container list中的容器)的狀態為running,將相應的將容器信息寫入到/var/lib/docker/containers/容器id/config.json文件中。然後監控容器(其實就是監控容器這個進程是否退出,退出,則釋放容器配置的網路,解除容器的掛載目錄,然後更新/var/lib/docker/containers/容器id/config.json文件)

之後,我們來分別看看容器掛載和網路的創建,是如何實現的。

磁碟掛載實現:

func (container *Container) EnsureMounted() error {n if mounted, err := container.Mounted(); err != nil {n return errn } else if mounted {n return niln }n return container.Mount()n}n

func Mounted(mountpoint string) (bool, error) {n mntpoint, err := os.Stat(mountpoint)n if err != nil {n if os.IsNotExist(err) {n return false, niln }n return false, errn }n parent, err := os.Stat(filepath.Join(mountpoint, ".."))n if err != nil {n return false, errn }n mntpointSt := mntpoint.Sys().(*syscall.Stat_t)n parentSt := parent.Sys().(*syscall.Stat_t)n return mntpointSt.Dev != parentSt.Dev, niln}n

掛載的過程:首先檢查/var/lib/docker/containerId/rootfs是否存在,如果不存在則返回,表示容器沒有掛載,在檢查父目錄/var/lib/docker/containerId是否存在,最後檢查父目錄和子目錄是否是同一個設備id。

如果沒有mount,則進行mout操作:

func (container *Container) Mount() error {n image, err := container.GetImage()n if err != nil {n return errn }n return image.Mount(container.RootfsPath(), container.rwPath())n}n

func (image *Image) Mount(root, rw string) error {n if mounted, err := Mounted(root); err != nil {n return errn } else if mounted {n return fmt.Errorf("%s is already mounted", root)n }n layers, err := image.layers()n if err != nil {n return errn }n // Create the target directories if they dont existn if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) {n return errn }n if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {n return errn }n // FIXME: @creack shouldnt we do this after going over changes?n if err := MountAUFS(layers, rw, root); err != nil {n return errn }n // FIXME: Create tests for deletionn // FIXME: move this part to change.gon // Retrieve the changeset from the parent and apply it to the containern // - Retrieve the changesn changes, err := Changes(layers, layers[0])n if err != nil {n return errn }n // Iterate on changesn for _, c := range changes {n // If there is a deleten if c.Kind == ChangeDelete {n // Make sure the directory existsn file_path, file_name := path.Dir(c.Path), path.Base(c.Path)n if err := os.MkdirAll(path.Join(rw, file_path), 0755); err != nil {n return errn }n // And create the whiteout (we just need to create empty file, discard the return)n if _, err := os.Create(path.Join(path.Join(rw, file_path),n ".wh."+path.Base(file_name))); err != nil {n return errn }n }n }n return niln}n

檢查是否已經掛載過,掛載過則直接返回信息:已經掛載過。否則進行相關的掛載,掛載過程如下:首先獲取鏡像的只讀層,根據傳入的參數創建讀寫層目錄:/var/lib/docker/containerId/rootfs/rw文件夾,然後進行auft的mout操作:其實就是將/var/lib/docker/containerId/rootfs/rw和對應的image的只讀成關聯上,也就是在只讀層上加一層讀寫層,最後syscall.mount()實現mout操作,syscall.mount的實現也是通過調用系統指令SYS_ACCT實現的。

func MountAUFS(ro []string, rw string, target string) error {n // FIXME: Now mount the layersn rwBranch := fmt.Sprintf("%v=rw", rw)n roBranches := ""n for _, layer := range ro {n roBranches += fmt.Sprintf("%v=ro:", layer)n }n branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)n return mount("none", target, "aufs", 0, branches)n}nnfunc mount(source string, target string, fstype string, flags uintptr, data string) (err error) {n return syscall.Mount(source, target, fstype, flags, data)n}n

接下來說網路的實現:網路的實現其實相對簡單,因為在上一篇博文中詳細介紹了networkManage這個struct的實例化過程,以及其中的欄位。實現過程如下:

func (container *Container) allocateNetwork() error {n iface, err := container.runtime.networkManager.Allocate()n if err != nil {n return errn }n container.NetworkSettings.PortMapping = make(map[string]string)n for _, port := range container.Config.Ports {n if extPort, err := iface.AllocatePort(port); err != nil {n iface.Release()n return errn } else {n container.NetworkSettings.PortMapping[strconv.Itoa(port)] = strconv.Itoa(extPort)n }n }n container.network = ifacen container.NetworkSettings.IpAddress = iface.IPNet.IP.String()n container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size()n container.NetworkSettings.Gateway = iface.Gateway.String()n return niln}n

func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {n ip, err := manager.ipAllocator.Acquire()n if err != nil {n return nil, errn }n iface := &NetworkInterface{n IPNet: net.IPNet{IP: ip, Mask: manager.bridgeNetwork.Mask},n Gateway: manager.bridgeNetwork.IP,n manager: manager,n }n return iface, niln}n

func (alloc *IPAllocator) Acquire() (net.IP, error) {n select {n case ip := <-alloc.queue:n return ip, niln default:n return net.IP{}, errors.New("No more IP addresses available")n }n return net.IP{}, niln}n

首先從runtime的networkManager獲取一個網路介面(實際的處理過程:從networkManager中的ip分配器中獲取一個ip,用networkmanage的mask,Gateway新建一個網卡),然後根據container中的指定的埠來做埠映射(實際處理過程:從networkManager中的埠分配器中娶一個埠,然後做port的iptable規則,如果出錯則埠分配器回收埠,最後將容器的映射埠存入容器的export數組中)

埠映射的處理:

func (iface *NetworkInterface) AllocatePort(port int) (int, error) {n extPort, err := iface.manager.portAllocator.Acquire()n if err != nil {n return -1, errn }n if err := iface.manager.portMapper.Map(extPort, net.TCPAddr{IP: iface.IPNet.IP, Port: port}); err != nil {n iface.manager.portAllocator.Release(extPort)n return -1, errn }n iface.extPorts = append(iface.extPorts, extPort)n return extPort, niln}n

func (alloc *PortAllocator) Acquire() (int, error) {n select {n case port := <-alloc.ports:n return port, niln default:n return -1, errors.New("No more ports available")n }n return -1, niln}n

func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error {n if err := mapper.iptablesForward("-A", port, dest); err != nil {n return errn }n mapper.mapping[port] = destn return niln}n

func (mapper *PortMapper) iptablesForward(rule string, port int, dest net.TCPAddr) error {n return iptables("-t", "nat", rule, "DOCKER", "-p", "tcp", "--dport", strconv.Itoa(port),n "-j", "DNAT", "--to-destination", net.JoinHostPort(dest.IP.String(), strconv.Itoa(dest.Port)))n}n

推薦閱讀:

基於OSS搭建私有(跨區域)Docker鏡像倉庫
docker怎麼修改拉取源從指定的國內倉庫拉取鏡像?
如何使用OpenDroneMap對航拍圖像快速建模
如何學習、了解kubernetes?
基於Docker、NodeJs實現高可用的服務發現

TAG:Docker |