cURL是我喜歡的開源軟件之一。雖然cURL的強大常常被認為是理所當然的,但我真心地認為它值得感謝和尊重。如果我們的工具箱失去了curl
,那些需要和網絡重度交互的人(我們大多數人都是這樣的)將會陷入到困境中。curl
速度快、體積小,並且和大多數好工具一樣,簡潔干凈,盡量不影響用戶,只做它們需要做的事情。
如果有人想使用curl
中的一種功能(比如UNIX套接字支持),而恰恰系統提供的包的配置不支持這種功能,或者包版本太老而不包含這種功能,由於curl
享有“數據傳輸的瑞士軍刀”的美譽,可以想象這種情況是有可能發生的。因此你會發現,你想要編譯一個包含你所需要的功能的curl
。編譯自己的軟件是令人害怕的,尤其是當你不擅長解決這類問題時,讓我們先暫時拋開這個事實,自定義你所使用的軟件將會是一次難以置信的、解放自身的體驗。
如果需要,你可以編譯自己的軟件,並且不受限於其它人交給你的軟件包,明確這一點將會給你帶來自信。突然間,你腦中可能會充斥着興奮的快感——你能夠按照你喜歡的方式安裝和配置任何軟件,並不需要接受其它人配置中的限制。人類常常喜歡給一樣東西加上自己的標記,這就是使用開源軟件的令人上癮的原因之一。
由於Docker文件系統的分離特性,它成為了完成這類事情最佳的選擇。你並不需要擔心安裝依賴庫或者運行一個糟糕的make install
類似的命令,會將你的本地文件系統弄亂。它能夠讓你在真實的環境中進行操作,並且允許你犯錯。這對於學習知識是一個極佳的工具,因為將事情完全弄糟是學習的必經之路,並且在容器中將事情弄糟,你能夠輕易地將這些容器丟棄,這種方式比弄糟自己的本地系統要安全多了。除此之外,如果你將這些步驟以腳本的形式寫入到Dockerfile
中,那么在后續重新構建時,構建步驟將保持一致。盡管Dockerfile
不能100%保證構建的結果,但這總比README
中的隨意描述好多了。
接下來讓我們一起構建一個Dockerfile
來創建一個支持HTTP2的、體積最小的、基於Alpine Linux的鏡像。重點將會放在減小鏡像體積和能夠100%自定義curl
上。
方法步驟
我們將會:
- 討論為什么我們要關注HTTP2?
- 簡要地看一遍
Dockerfile
,讓你對構建過程有初步了解。 - 討論為什么將Alpine作為基礎鏡像?
- 詳細解釋
Dockerfile
,一步一步地理解它。 - 構建並且運行鏡像。
為什么選擇HTTP2?
由於HTTP的危急現狀,HTTP/2成為了HTTP的代替品。HTTP/2並不是對HTTP協議的完全重寫,HTTP方法,狀態碼,語義都保留了下來,並且應該能夠使用和HTTP/1.x(可能有細微調整)一樣的API來表示HTTP/2協議。
HTTP/2協議重點關注性能,尤其是端用戶的接收延遲、網絡和服務器資源的使用。其中一個主要的目標就是從瀏覽器訪問一個Web網站可以只通過一條連接來實現。
簡要地說,HTTP2想要解決HTTP/1.1的一些缺點,包括性能問題。通過對前面鏈接中的樣本用例進行測試,CloudFlare聲明HTTP/2對我的電腦會有4~8倍的速度提升。能夠提升網頁4~8倍的速度?是的,你沒有聽錯。
Dockerfile
這就是支持HTTP2的curl的Dockerfile
:
FROM alpine:edge # For nghttp2-dev, we need this respository. RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/testing >>/etc/apk/repositories ENV CURL_VERSION 7.50.1 RUN apk add --update --no-cache openssl openssl-dev nghttp2-dev ca-certificates RUN apk add --update --no-cache --virtual curldeps g++ make perl && \ wget https://curl.haxx.se/download/curl-$CURL_VERSION.tar.bz2 && \ tar xjvf curl-$CURL_VERSION.tar.bz2 && \ rm curl-$CURL_VERSION.tar.bz2 && \ cd curl-$CURL_VERSION && \ ./configure \ --with-nghttp2=/usr \ --prefix=/usr \ --with-ssl \ --enable-ipv6 \ --enable-unix-sockets \ --without-libidn \ --disable-static \ --disable-ldap \ --with-pic && \ make && \ make install && \ cd / && \ rm -r curl-$CURL_VERSION && \ rm -r /var/cache/apk && \ rm -r /usr/share/man && \ apk del curldeps CMD ["curl"]
以上的構建過程大概做了以下事情:
- 我們安裝了一些包,里面包含了我們所需要的、支持SSL(HTTPS)和HTTP2的庫。
- 我們安裝了一些編譯cURL所必需的庫。
- 我們下載和解壓了cURLDE 源代碼(在寫作時的最新穩定版)。
- 我們配置,編譯,然后安裝了
curl
。 - 我們清理了那些構建需要的、但是不想保留在最終鏡像中的依賴。
- 我們將默認的
CMD
設置為curl
。
為什么選擇Alpine?
Alpine Linux是體積最小的Linux發行版,它重點關注於安全和速度。使用apk
能夠很快地安裝軟件包,默認情況下,鏡像只包含了完成基礎UNIX任務所需要的東西 ,因此相對於其它Docker基礎鏡像,體積會更小。
對比常用的、沒有壓縮過的基礎鏡像(在寫作時使用的是:latest標簽):
Alpine
- 4.8MBUbuntu
- 124.8 MBDebian
- 125.1MBCentos
- 196MB
想象一下,在網絡上一次又一次地下載拉取這些鏡像
你是否正在考慮這能否對硬盤和帶寬產生同樣25倍價值的提升?在某些情況下,是相等的,但是Alpine每天都在不斷地改進和提高,並且提供了很多殺手鐧級別的特性,比如說通過文件名來查找(例子:需要定位哪一個apk
包包含了二進制文件mke2fs
,完全沒有問題。)。在使用其它工具時,我必須花費大量的時間來學習它們奇怪的使用方式,對此我感到十分憤怒,不同於這些工具,我對使用Alpine十分高興,並且它不斷地給我驚喜。尤其是在使用一些小工具類型的容器時,比如說curl
,鏡像體積的縮小對我來說非常棒。
詳細的構建步驟
讓我們更深入地了解Dockerfile。
FROM alpine:edge # For nghttp2-dev, we need this respository. RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/testing >>/etc/apk/repositories
在Alpine的edge分支中,nghttp2
包(支持cURL中的HTTP2所必需的包)只有在testing倉庫有效,因此這幾行命令確保了當我們執行apk install
時nghttp2
包能夠被正確安裝。閱讀“如何讓cURL支持HTTP2”的文檔就會發現, nghttp2
庫是必需的(由於HTTP2所帶來的復雜性),並且在Alpine的歸檔中閑逛時,發現了edge分支中nghttp2
只在testing倉庫有效。
ENV CURL_VERSION 7.50.1
當cURL發布了新版本,我們想要更新鏡像,我們僅僅需要修改這個文件的一處位置——環境變量,7.50.1
表示在寫作時cURL最新的穩定版。
RUN apk add --update --no-cache openssl openssl-dev nghttp2-dev ca-certificates
這些是我們想要最終保留在鏡像的依賴,默認證書和庫是為了讓curl
支持SSL(HTTPS連接)。注意—no-cache
,這個確保了apk
不會使用多余的硬盤空間來緩存包位置查找的結果,最終就會節省鏡像的空間。
下一條RUN命令只會產生一個文件層(因此我們可以安裝一些依賴,使用它們,然后清除它們,不將它們保留在最終鏡像中)。這條命令內容比較多,讓我們一步一步來看它們到底做了什么操作。
RUN apk add --update --no-cache --virtual curldeps g++ make perl && \
以上全都是成功編譯和安裝curl
所需要的工具。--virtual
是一個非常有用的apk特性——虛擬包。你可以給予包的集合一個標簽,然后通過使用一條命令 apk del virtual-pkg-name
來將它們全部清除。
wget https://curl.haxx.se/download/curl-$CURL_VERSION.tar.bz2 && \ tar xjvf curl-$CURL_VERSION.tar.bz2 && \ rm curl-$CURL_VERSION.tar.bz2 && \ cd curl-$CURL_VERSION && \
獲得cURL的源碼壓縮包,解壓它,刪除壓縮包(我們在解壓后就不需要它了),然后使用cd
命令進入到源文件目錄。
./configure \ --with-nghttp2=/usr \ --prefix=/usr \ --with-ssl \ --enable-ipv6 \ --enable-unix-sockets \ --without-libidn \ --disable-static \ --disable-ldap \ --with-pic && \ make && \ make install && \
在熟悉的./configure;make;make install
命令的基礎上加上了一些cURL特有的偏好設置。--with-nghttp2=/usr
就是用來配置HTTP2支持的,由於我們將nghttp2-dev
安裝在Aline的/usr/lib
目錄下,在構建cURL的時候,程序會自動在/usr
下的lib目錄尋找一個包配置文件。因此,你可能在其他的例子中看到參數設置為/usr/local
或者其它目錄。
大多數的其它參數(除了—with-ssl
)都是都拷貝自上游對curl包的請輸入鏈接描述APKBUILD文件。由於Alpine的包維護者比較可靠,因此我決定復用這些已經存在的配置。如果我對這么做感到太魯莽,那么我將會深入進去,然后從底層的角度來決定哪些我需要,哪些不需要,但是我還是希望它們包含UNIX套接字和IPV6的支持,因此我保留了這些已存在的配置。
cd / && \ rm -r curl-$CURL_VERSION && \ rm -r /var/cache/apk && \ rm -r /usr/share/man && \ apk del curldeps
以上全都是清除工作。
保留構建目錄(也就是二進制文件被安裝的地方),去除源代碼目錄,運行apk del curldepsenter code here
命令來清除我們之前創建的虛擬包,接下來再去除/var/cache/apk
(這是包緩存,老實說,我也不清楚為什么使用了—no-cache
選項,緩存依舊存在)和/usr/share/man
目錄(幫助手冊,在man命令沒有被安裝的情況下,這是無用的)。其中一些清除操作,尤其是緩存和幫助頁面的清除,某種程度上可以說是對縮小鏡像體積的一種怪癖,畢竟它們實際上不會超過1MB。這些都是我通過運行du | sort -n
后,認為在最終鏡像中可能不必要的內容,我只能說,我狂熱地追求盡可能地縮小鏡像體積。
由於以上的這些操作都屬於同一個RUN命令,因此這最終會產生一個相對小的鏡像層,盡管在命令最開始的時候,我們為了構建最終的產品,安裝了將近212MB的依賴。如果這些操作分布在不同的層,清除操作實際上不會真正地在最終鏡像上刪除這些文件,相反,只是將這些文件隱藏了起來。
最后一條:
CMD ["curl"]
docker run image
命令將會默認調用curl
命令。當然這也能夠替換為ENTRYPOINT
,但是我並不介意CMD
能夠簡單地通過docker run
被重新賦值。
構建並且運行鏡像
首先是構建,將Dockerfile
丟進一個空目錄下,然后在這個目錄下運行:
$ docker build -t yourname/curl .
一旦構建完鏡像,運行鏡像就顯得非常直接了。讓我們來檢查看看一切是否按照nghttp2.org上描述的那樣工作。-s
表示啟動安靜模式,--http2
表示使用HTTP2協議,-I
能夠返回請求頭,以此驗證我們使用了正確的協議。
$ docker run yourname/curl curl -s --http2 -I https://nghttp2.org HTTP/2 200 date: Sat, 06 Aug 2016 21:47:31 GMT content-type: text/html last-modified: Thu, 21 Jul 2016 14:06:56 GMT etag: "5790d700-19e1" accept-ranges: bytes content-length: 6625 x-backend-header-rtt: 0.00166 strict-transport-security: max-age=31536000 server: nghttpx nghttp2/1.14.0-DEV via: 2 nghttpx x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff
很好,一切正常工作。並且最終的鏡像體積保持在16MB左右。這對於私人訂制的curl構建來說是不錯的,畢竟編譯curl需要上百MB的依賴的支持。
結論
- Alpine Linux非常棒。
- 從零構建你自己的工具是難以想象的,但確實令人興奮。
- Docker非常適合於從源代碼構建工具。
- 你能夠擁有支持HTTP2的cURL工具。