我的docker隨筆13:docker源碼編譯進階篇

本文與前面文章相同,但多了一些分析的步驟。

一、環境搭建

docker的編譯,需要在宿主機預先安裝docker軟件。因爲編譯docker的源碼時,會構建一個docker鏡像並運行,在這個容器裏面進行build操作。由於這個容器已經包含了go語言環境,故宿主機無須額外安裝golang。
宿主機系統:ubuntu 16.04 64bit
宿主機docker版本:

docker -v
Docker version 17.10.0-ce, build f4ffd25

二、下載源碼

docker的github官方網站爲:https://github.com/docker/docker-ce/
docker以每月發佈一個版本的節奏進行開發。命名規則爲:年份-月份-ce,其中ce表示社區版本。截至本文撰寫時,最新版本爲v18.02.0-ce,但下一版本v18.03.0的rc版本已經釋放出來了,主分支的版本號爲v18.03.0-ce-dev(帶dev表示開發階段),本文編譯得到的版本即爲v18.03.0-ce-dev
發行版本下載地址:
https://github.com/docker/docker-ce/releases
本文編譯的源碼,無實際路徑無關。
下載源碼:

git clone https://github.com/docker/docker-ce

進入docker-ce目錄:

cd docker-ce

三、編譯過程

本節編譯docker-ce主分支代碼,經過分析後,對其編譯腳本進行了修改、調整,以加速編譯時間。

Makefile分析

這裏先跟蹤頂層Makefile到具體平臺的編譯Makefile文件。
首先是工程目錄docker-ce的Makefile,由於是ubuntu系統,因此編譯的是deb包,相關命令:

.PHONY: deb
deb: ## build deb packages
	$(MAKE) VERSION=$(VERSION) CLI_DIR=$(CLI_DIR) ENGINE_DIR=$(ENGINE_DIR) -C $(PACKAGING_DIR) deb

從命令看到,調用的是components\packaging\deb目錄的Makefile(默認情況下,執行的是Makefile文件), 該Makefile文件關於deb的編譯命令如下:

deb: ubuntu debian raspbian

而ubuntu平臺又涉及2個發行版本:

ubuntu: ubuntu-xenial ubuntu-trusty

關於ubuntu-xenial,編譯命令:

ubuntu-xenial: ## build ubuntu xenial deb packages
	docker build -t debbuild-$@/$(ARCH) -f $(CURDIR)/$@/Dockerfile.$(ARCH) .
	docker run --rm -i \
		-e DEB_VERSION=$(DEB_VERSION) \
		-e VERSION=$(VERSION) \
		-e DOCKER_GITCOMMIT=$(GITCOMMIT) \
		-v $(CURDIR)/debbuild/$@:/build \
		-v $(ENGINE_DIR):/engine \
		-v $(CLI_DIR):/cli \
		-v $(CURDIR)/systemd:/root/build-deb/systemd \
		debbuild-$@/$(ARCH)
	$(CHOWN) -R $(shell id -u):$(shell id -g) debbuild/$@

大意是先用docker build構建一個鏡像(涉及到Dockerfile,後文再提及),然後運行這個鏡像,運行命令需要設置環境變量(VERSION等),還有掛載目錄($(ENGINE_DIR):/engine等),執行的命令是docker鏡像默認的命令。構建docker鏡像命令如下:

docker build -t debbuild-$@/$(ARCH) -f $(CURDIR)/$@/Dockerfile.$(ARCH) .

針對ubuntu16.04(代號爲ubuntu-xenial),其中編譯生成的鏡像名稱爲debbuild-@/@/(ARCH),展開宏定義,則變成debbuild-ubuntu-xenial/x86_64,而由-f指定Dockerfile,則爲ubuntu-xenial/ Dockerfile.x86_64
ockerfile.x86_64需要訪問https://golang.org下載go語言安裝包,該網站國內一般無法訪問,因此需要想其它方法。

編譯流程

Dockerfile.x86_64看出,默認執行腳本爲build-deb,該文件位於Makefile同一目錄,大致內容爲進行4個組件的編譯,再編譯docker源碼,然後拷貝生成的文件到指定目錄。
具體細節本文不展開。

修改編譯流程

在編譯docker源碼過程中,每次都會構建docker鏡像,而在docker裏面,每次都需要從github.com上克隆4個必要的組件源碼(並進行編譯),這個過程在起初時是必要的,但如果在實際開發中只需要修改個別源碼進行編譯的話,跑完整個流程就顯示比較繁瑣了。修改思路有:
預先製作好包含go語言環境的docker(已經製作好singula/docker-dev),基於這個docker鏡像再次製作編譯所需鏡像。
將編譯的組件源碼目錄掛載到主機目錄,這樣不需要每次都從網絡上下載了。

修改工程目錄Makefile,參考原來的deb,新加mydeb編譯:

# build mydeb for ubuntu-xenial(16.04)
.PHONY: mydeb
mydeb: ## build deb packages
	$(MAKE) VERSION=$(VERSION) CLI_DIR=$(CLI_DIR) ENGINE_DIR=$(ENGINE_DIR) -C $(PACKAGING_DIR)/deb -f myMakefile ubuntu-xenial

components\packaging\deb目錄,參考Makefile,新建myMakefile,關鍵內容:

.PHONY: xenial_docker
xenial_docker: ## build the docker
	docker build -t debbuild-ubuntu-xenial/$(ARCH) -f $(CURDIR)/ubuntu-xenial/myDockerfile.$(ARCH) .

.PHONY: ubuntu-xenial
ubuntu-xenial: ## build ubuntu xenial deb packages
	mkdir -p $(CURDIR)/src/tini $(CURDIR)/src/libnetwork \
		$(CURDIR)/src/runc $(CURDIR)/src/containerd
	docker run --rm -i \
		-e DEB_VERSION=$(DEB_VERSION) \
		-e VERSION=$(VERSION) \
		-e DOCKER_GITCOMMIT=$(GITCOMMIT) \
		-v $(CURDIR)/debbuild/$@:/build \
		-v $(ENGINE_DIR):/engine \
		-v $(CLI_DIR):/cli \
		-v $(CURDIR)/systemd:/root/build-deb/systemd \
		-v $(CURDIR)/src/tini:/go/tini \
		-v $(CURDIR)/src/libnetwork:/go/src/github.com/docker/libnetwork \
		-v $(CURDIR)/src/runc:/go/src/github.com/opencontainers/runc \
		-v $(CURDIR)/src/containerd:/go/src/github.com/containerd \
		debbuild-$@/$(ARCH) /root/build-deb/mybuild-deb
	$(CHOWN) -R $(shell id -u):$(shell id -g) debbuild/$@
	cp $(CURDIR)/debbuild/$@/*.deb $(TOP_DIR)

該文件將構建docker和編譯拆分出來,編譯部分,將編譯所需的組件目錄掛載位於deb同級目錄的src目錄。最後將生成的deb包拷貝到工程目錄。
參考編譯腳本build-deb新建mybuild-deb文件。將TMP_GOPATH="/go" hack/dockerfile/install/install.sh $component修改爲TMP_GOPATH="/go" hack/dockerfile/myinstall/install.sh $component
components\engine\hack\dockerfile目錄下,拷貝install爲myinstall,修改其中的文件:
containerd.installer、proxy.installer、runc.installer、tini.installer。修改git clone的處理邏輯。
以tini.installer爲例,原來內容爲:

TINI_COMMIT=949e6facb77383876aeff8a6944dde66b3089574

install_tini() {
	echo "Install tini version $TINI_COMMIT"
	git clone https://github.com/krallin/tini.git "$GOPATH/tini"
	cd "$GOPATH/tini"
	git checkout -q "$TINI_COMMIT"
	cmake .
	make tini-static
	mkdir -p ${PREFIX}
	cp tini-static ${PREFIX}/docker-init
}

修改後爲

TINI_COMMIT=949e6facb77383876aeff8a6944dde66b3089574

install_tini() {
	echo "Install tini version $TINI_COMMIT"
	
	if [ ! -d $GOPATH/tini/.git ]; then
	    echo "will clone tini..."
	    git clone https://github.com/krallin/tini.git "$GOPATH/tini"
	else
	    echo "tini exist..."
	fi
	
	cd "$GOPATH/tini"
	git checkout -q "$TINI_COMMIT"
	cmake .
	make tini-static
	mkdir -p ${PREFIX}
	cp tini-static ${PREFIX}/docker-init
}

即,判斷tini是否被下載,如果是,則不再下載。如否,則下載之。
其它文件同理。

編譯及生成文件

在docker-ce目錄輸入make mydeb即可進行編譯。最後生成的安裝包位於同一目錄下。
如果從未編譯過docker-ce,編譯耗時約在20~30分鐘(大約數,根據網絡和機器性能而定),如果已編譯過docker-ce,修改源碼後,再次編譯,則只需要幾分鐘到十幾分鍾即可。大減少編譯時間。
安裝
將得到的deb包存放到本機或其它ubuntu系統上,執行以下命令進行安裝:

# dpkg -i docker-ce_18.04.0~ce~dev~git20180312.035344.0.37dff31-0~ubuntu_amd64.deb

驗證其版本號:

# docker -v
Docker version 18.04.0-ce-dev, build 2b42807

到此,docker的編譯結束。

備註

本文所用方法,考慮了與官方docker源碼不發生衝突,但在合併時,還是要注意本文提到的修改的文件。至於是否有其它更好的辦法,則待後面發現時再嘗試。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章