[Docker] 用 Docker 建立專案的 build 機器
平常在公司的專案裡,都會有個叫 build machine 的東東,
上面通常要裝一堆函式庫,然後再把原始碼放上去,
在那個機器上編譯出可執行碼~
後來這個 build machine 從實體機器演進到虛擬機器 (VM) 了,
我們可以每個 RD 都自己依需求架好幾台 VM,然後在上面編譯~
最近聽到公司另一個部門同事的分享,他們用 Docker 技術來建立 build machine,
因為他們每次只要 Linux kernel 有更新,就得立刻跟進,
要架設 VM 版的 build machine 還是頗花時間…
聽了他們的分享之後,想說也來研究一下我們自己的專案是不是也能這麼做呢?
1. 初步規劃
以我們的 build machine 來說,我們只有一套原始碼,
但是需要在 CentOS 5.4, 6.2, 和 7.0 三種平台上編譯~
需要安裝的 3rd-party 函式庫都差不多,只有很小部分的差異…
因此我決定
– 寫一個 shell script,負責從一個 Dockerfile 的範本中,根據平台產生出對應的 Dockerfile
– 再用 Dockerfile 建出 base image,
– 最後就可以在這 base image 跑 container 來執行編譯工作了
2. 準備 Dockerfile 範本
這邊先講我寫的 Dockerfile 範本~
首先 FROM 指令這邊,因為每個平台要有不同的 base image,
因此我定義了一個 __FROM_CENTOS_TAG__,
待會再用 shell script 根據平台來把它替換成正確的 base image 名稱:
FROM __FROM_CENTOS_TAG__
接著有一大段使用 RUN 指令安裝 3rd-party 函式庫以及工具的部分,
這邊因為對每個平台來說都一樣,所以不需要修改:
# Install libraries RUN yum -y install glibc-devel glibc-devel.i686 RUN yum -y install libstdc++ libstdc++.i686 RUN yum -y install postgresql-devel RUN yum -y install pcre-devel RUN yum -y install libpng-devel RUN yum -y install libpcap-devel RUN yum -y install openssl-devel openssl-devel.i686 RUN yum -y install python-devel # Install tools RUN yum -y install make gcc-c++ RUN yum -y install mkisofs RUN yum -y install tar bzip2 zip unzip RUN yum -y install patch RUN yum -y install bison flex RUN yum -y install gettext RUN yum -y install wget RUN yum -y install curl RUN yum -y install which RUN yum -y install openssh-clients
接著使用 WORKDIR 指令,將工作目錄切換成 /tmp,
因為待會會開始用 wget 抓一些工具下來編譯,想說就抓到 /tmp 目錄下:
WORKDIR /tmp
接下來的幾個動作稍微麻煩一點…
我需要在 CentOS 5.4 的平台上,安裝 python 2.6 (原本預設值是 2.4),
同時得把 curl 升級到 7.19.7,
還得在各個平台上安裝 pylint 好讓我們可以用 pylint 靜態檢查 python 程式有無問題,
以及安裝 cppcheck 靜態檢查 C/C++ 程式的問題~
這邊因為跟平台有關,因此我也定義了幾個會被取代的變數 (像 __INSTALL_PYTHON26__),
讓 shell script 來決定正確的變數值,這樣 Dockerfile 範本可以保持簡潔:
# Install python 2.6 RUN [ "__INSTALL_PYTHON26__" = "0" ] || (wget http://www.python.org/ftp/python/2.6.8/Python-2.6.8.tgz && tar zxvf Python-2.6.8.tgz && cd Python-2.6.8 && ./configure --with-threads && make && make install) # Install curl 7.19.7 RUN [ "__INSTALL_CURL7_19_7__" = "0" ] || (wget https://github.com/csexton/curl-7.19.7/archive/master.zip -O master.zip && unzip master.zip && cd curl-7.19.7* && ./configure --disable-ldap --disable-ldaps --prefix /usr --libdir /usr/lib64 && make && make install) # Install pylint RUN wget https://pypi.python.org/packages/source/l/logilab-common/logilab-common-0.58.3.tar.gz && tar zxvf logilab-common-0.58.3.tar.gz && cd logilab-common-* && python setup.py build && python setup.py install RUN wget https://pypi.python.org/packages/source/l/logilab-astng/logilab-astng-0.24.1.tar.gz && tar zxvf logilab-astng-0.24.1.tar.gz && cd logilab-astng-* && python setup.py build && python setup.py install RUN wget https://pypi.python.org/packages/source/p/pylint/pylint-0.26.0.tar.gz && tar zxvf pylint-0.26.0.tar.gz && cd pylint-* && python setup.py build && python setup.py install # Install cppcheck RUN [ "__INSTALL_CPPCHECK__" = "0" ] || (wget http://downloads.sourceforge.net/project/cppcheck/cppcheck/1.65/cppcheck-1.65.tar.gz && tar zxvf cppcheck*.gz && cd cppcheck* && make SRCDIR=build CFGDIR=cfg HAVE_RULES=yes && make install)
在上面的指令裡面,要注意我都是用 && 來連結各個指令,
這邊是不能用 ; 的,否則如果前面指令錯誤的話,分號 ; 後面的指令依然會被執行,
這樣建立出來的 base image 可能是有部分東西沒有裝成功的喔~
最後我還準備了一份預先改好的 .bashrc,使用 COPY 指令複製進 base image,
這樣之後進去編譯環境時,可以有自己習慣使用的 alias 或設定~
以及建立 ssh key pair,
方便把 ssh public key 加到常用的 server 上面,避免 ssh 時要一直打密碼:
# Prepare bashrc COPY .bashrc ~/ # Generate ssh key RUN ssh-keygen -f ~/.ssh/id_rsa -N ""
這是完整的 Dockerfile 範本:
FROM __FROM_CENTOS_TAG__ # Install libraries RUN yum -y install glibc-devel glibc-devel.i686 RUN yum -y install libstdc++ libstdc++.i686 RUN yum -y install postgresql-devel RUN yum -y install pcre-devel RUN yum -y install libpng-devel RUN yum -y install libpcap-devel RUN yum -y install openssl-devel openssl-devel.i686 RUN yum -y install python-devel # Install tools RUN yum -y install make gcc-c++ RUN yum -y install mkisofs RUN yum -y install tar bzip2 zip unzip RUN yum -y install patch RUN yum -y install bison flex RUN yum -y install gettext RUN yum -y install wget RUN yum -y install curl RUN yum -y install which RUN yum -y install openssh-clients WORKDIR /tmp # Install python 2.6 RUN [ "__INSTALL_PYTHON26__" = "0" ] || (wget http://www.python.org/ftp/python/2.6.8/Python-2.6.8.tgz && tar zxvf Python-2.6.8.tgz && cd Python-2.6.8 && ./configure --with-threads && make && make install) # Install curl 7.19.7 RUN [ "__INSTALL_CURL7_19_7__" = "0" ] || (wget https://github.com/csexton/curl-7.19.7/archive/master.zip -O master.zip && unzip master.zip && cd curl-7.19.7* && ./configure --disable-ldap --disable-ldaps --prefix /usr --libdir /usr/lib64 && make && make install) # Install pylint RUN wget https://pypi.python.org/packages/source/l/logilab-common/logilab-common-0.58.3.tar.gz && tar zxvf logilab-common-0.58.3.tar.gz && cd logilab-common-* && python setup.py build && python setup.py install RUN wget https://pypi.python.org/packages/source/l/logilab-astng/logilab-astng-0.24.1.tar.gz && tar zxvf logilab-astng-0.24.1.tar.gz && cd logilab-astng-* && python setup.py build && python setup.py install RUN wget https://pypi.python.org/packages/source/p/pylint/pylint-0.26.0.tar.gz && tar zxvf pylint-0.26.0.tar.gz && cd pylint-* && python setup.py build && python setup.py install # Install cppcheck RUN [ "__INSTALL_CPPCHECK__" = "0" ] || (wget http://downloads.sourceforge.net/project/cppcheck/cppcheck/1.65/cppcheck-1.65.tar.gz && tar zxvf cppcheck*.gz && cd cppcheck* && make SRCDIR=build CFGDIR=cfg HAVE_RULES=yes && make install) # Prepare bashrc COPY .bashrc /root/ # Generate ssh key RUN ssh-keygen -f ~/.ssh/id_rsa -N ""
3. 撰寫 shell script 來產生 Dockerfile
shell script 本身並不複雜,只需要根據 command line 給的平台參數,
來決定 base image 的名稱,以及 cppcheck 或是 python 2.6 等等是否需要安裝,
決定好之後,就可以從 Dockerfile 範本中 (本例中的 Dockerfile.template) 複製一份,
把變數值用 sed 修改好就行了~
最後再執行 docker build 將 base image 建立好:
#!/bin/sh . ./common.sh # Get build platform from command line parameter BUILD_PLATFORM="$1" INSTALL_CPPCHECK=1 INSTALL_PYTHON26=0 INSTALL_CURL7_19_7=0 case "${BUILD_PLATFORM}" in centos5_4) FROM_CENTOS_TAG="gpmidi/centos-5.4" INSTALL_CPPCHECK=0 INSTALL_PYTHON26=1 INSTALL_CURL7_19_7=1 ;; centos6_2) FROM_CENTOS_TAG="centos:6" ;; centos7) FROM_CENTOS_TAG="centos:7.0.1406" ;; *) echo "Syntax: $0 <centos5_4|centos6_2|centos7>" exit ;; esac # Replace tokens in Dockerfile cp -f Dockerfile.template Dockerfile for VAR in FROM_CENTOS_TAG \ INSTALL_PYTHON26 \ INSTALL_CURL7_19_7 \ INSTALL_CPPCHECK do sed -i "s|__${VAR}__|$(eval echo \$${VAR})|g" Dockerfile done docker build -t "$(get_image_name)" .
4. 建立 base image
上述的 shell script 程式寫好後,就可以很方便地建立出某個平台上的 base image,例如:
./build_base_image.sh centos7
5. 從 base image 上執行 container,來執行專案的編譯
這邊我一樣寫了一個 run_container.sh 的 shell script 程式方便使用,
只要執行 ./run_container.sh centos7,
就能從剛建立好的 CentOS 7 的 base image 跑出一個 container 來~
程式裡面基本上就是執行 docker run,
加上 -v 參數好讓原始碼的目錄可以被掛載 (mount) 到 container 裡面,
另外用 –env 設定一個 NO_PYLINT 的環境變數,
這樣的環境變數可以被 container 裡面的 Makefile 吃到來做控制的~
#!/bin/sh . ./common.sh # Get build platform from command line parameter BUILD_PLATFORM="$1" if [ -z "${BUILD_PLATFORM}" ]; then echo "Syntax: $0 <centos5_4|centos6_2|centos7>" exit fi docker run -it --rm -v ~/p4:/p4:Z -w /p4 --env NO_PYLINT=1 "$(get_image_name)" /bin/bash
進到 container 裡面之後,就可以執行 make 指令來編譯專案的原始碼了:
testuser@localhost ~ $ ./run_container.sh centos7 Usage of loopback devices is strongly discouraged for production use. Either use `--storage-opt dm.thinpooldev` or use `--storage-opt dm.no_warn_on_loop_devices=true` to suppress this warning. [root@060facde7f9c src]# make
用上面的這些步驟,就可以很方便的建立出給某個平台用的 base image,
然後可以用這 base image 跑出來的 container 來執行專案的編譯,
不需要再另外架設 build machine (VM) 了~
另一個很大的好處是可以將這幾個(很小的)程式傳給其他同事,
讓他們也能很快速的建立出一樣的編譯環境,
不用再擔心每個人自己架 build machine 可能架的都不太一樣~
Docker 這個應用真的很不錯喔~^^