【51CTO.com快譯】
在構(gòu)建容器化的應(yīng)用時(shí),開發(fā)人員往往需要某種方法來(lái)引導(dǎo)啟動(dòng)目標(biāo)容器,以對(duì)其進(jìn)行代碼級(jí)別的測(cè)試。盡管業(yè)界有許多方法可以實(shí)現(xiàn)該目的,但Docker Compose是目前最受歡迎的一種方法。它能夠讓如下兩個(gè)方面變得容易實(shí)現(xiàn):
- 指定在開發(fā)過程中需要啟動(dòng)的容器。
- 設(shè)置一套快速的代碼測(cè)試調(diào)試(code-test-debug),以方便開發(fā)循環(huán)。
通常情況下,團(tuán)隊(duì)事先編寫一個(gè)docker-compose.yml文件,指定開發(fā)所需的所有內(nèi)容,并將其提交給存儲(chǔ)庫(kù)。然后,每個(gè)開發(fā)人員只需運(yùn)行docker-compose up,即可啟動(dòng)測(cè)試其代碼所需的所有容器。
不過,要讓docker-compose的設(shè)置能夠達(dá)到最佳性能狀態(tài),例如:在不到一分鐘之內(nèi)啟動(dòng)開發(fā)環(huán)境,并且在幾秒鐘內(nèi)完成對(duì)每個(gè)更改的測(cè)試,這些都需要團(tuán)隊(duì)花費(fèi)大量的工作。在這些準(zhǔn)備過程中,由于各個(gè)開發(fā)人員每天花費(fèi)在測(cè)試其代碼上的時(shí)間各不相同,而且任何細(xì)微的改動(dòng),都可能會(huì)對(duì)整個(gè)開發(fā)團(tuán)隊(duì)的生產(chǎn)力產(chǎn)生巨大的影響。因此,我們有必要在此討論他們?cè)诰帉慏ocker Compose時(shí)常見的五大錯(cuò)誤,及其對(duì)應(yīng)的解決方法。
錯(cuò)誤1:頻繁地進(jìn)行容器重建
Docker的構(gòu)建往往比較耗時(shí),特別是每次針對(duì)代碼的變更開展測(cè)試的時(shí)候。如果能夠節(jié)省此方面的時(shí)間,那么對(duì)于加快開發(fā)周期來(lái)說(shuō)是十分有益的。過去,對(duì)于非容器化的應(yīng)用,我們通常會(huì)采取如下傳統(tǒng)的工作流程:
- 編寫代碼
- 構(gòu)建
- 運(yùn)行
多年來(lái),業(yè)界持續(xù)優(yōu)化該流程,并提出了諸如:針對(duì)編譯語(yǔ)言的增量構(gòu)建和熱重載(hot reloading)等實(shí)用技巧。隨著容器技術(shù)的出現(xiàn),我們?cè)诂F(xiàn)有的工作流程中增加了docker構(gòu)建的步驟,如下圖所示。
- 編寫代碼
- 構(gòu)建
- Docker構(gòu)建
- 運(yùn)行
當(dāng)然,如果構(gòu)建得不好,那么docker構(gòu)建步驟也可能會(huì)帶來(lái)額外的時(shí)間開銷。例如:使用apt-get進(jìn)行依賴項(xiàng)的重載步驟。有時(shí)候,這些步驟可能會(huì)讓整個(gè)測(cè)試過程比添加Docker之前還要慢。
解決方案:在Docker外部運(yùn)行代碼
第一種解決方法是在Docker Compose中啟動(dòng)所有的依賴項(xiàng),然后在本地運(yùn)行測(cè)試代碼。此舉模仿了非容器化應(yīng)用開發(fā)的工作流程。
您只需向localhost公布依賴關(guān)系,然后將正在使用的服務(wù)指向所有的localhost:地址即可。但是,該方法并非永遠(yuǎn)可行,如果您正在使用的是代碼依賴容器鏡像中的內(nèi)置元素時(shí),那么用戶電腦就不一定能夠訪問到具體內(nèi)容。
解決方案:最大化緩存,以優(yōu)化Dockerfile
如果必須構(gòu)建Docker鏡像,那么我們可以編寫Dockerfile,通過最大化緩存,將Docker的構(gòu)建時(shí)間從原來(lái)的10分鐘壓縮至1分鐘。
在生產(chǎn)環(huán)境中,Dockerfile的典型模式是通過將單個(gè)命令鏈接到一條RUN語(yǔ)句中,來(lái)減少層級(jí)的數(shù)量。畢竟,在開發(fā)過程中鏡像的大小并不重要,重要的是層級(jí)的數(shù)量。下面展示的是在生產(chǎn)環(huán)境中的一個(gè)Dockerfile文件:
- RUN \
- go get -d -v \
- && go install -v \
- && go build
不過,該命令在每次被重新運(yùn)行時(shí),Docker都會(huì)重新下載所有的依賴項(xiàng),并重新安裝它們。我們可以通過增量構(gòu)建(incremental build)來(lái)提供效率。
同時(shí),您可以將開發(fā)專用的Dockerfile其分成幾個(gè)短小的步驟,從而使得那些經(jīng)常更改的代碼步驟被排到最后,而將鮮少更改的步驟(例如拉式依賴關(guān)系)被放在首位。因此,在重建Dockerfile時(shí),您不必構(gòu)建整個(gè)項(xiàng)目,而只需構(gòu)建那些被已更改的少量末尾塊即可。有關(guān)此方面的案例,您可以參閱以下用于Blimp(請(qǐng)參見--https://kelda.io/blimp)開發(fā)的Dockerfile。通過遵循上述方法,您可以將繁瑣的構(gòu)建過程縮減到了幾秒鐘之內(nèi)完成。
- FROM golang:1.13-alpine as builder
- RUN apk add busybox-static
- WORKDIR /go/src/github.com/kelda-inc/blimp
- ADD ./go.mod ./go.mod
- ADD ./go.sum ./go.sum
- ADD ./pkg ./pkg
- ARG COMPILE_FLAGS
- RUN CGO_ENABLED=0 go install -i -ldflags "${COMPILE_FLAGS}" ./pkg/...
- ADD ./login-proxy ./login-proxy
- RUN CGO_ENABLED=0 go install -i -ldflags "${COMPILE_FLAGS}" ./login-proxy/...
- ADD ./registry ./registry
- RUN CGO_ENABLED=0 go install -i -ldflags "${COMPILE_FLAGS}" ./registry/...
- ADD ./sandbox ./sandbox
- RUN CGO_ENABLED=0 go install -i -ldflags "${COMPILE_FLAGS}" ./sandbox/...
- ADD ./cluster-controller ./cluster-controller
- RUN CGO_ENABLED=0 go install -i -ldflags "${COMPILE_FLAGS}" ./cluster-controller/...
- RUN mkdir /gobin
- RUN cp /go/bin/cluster-controller /gobin/blimp-cluster-controller
- RUN cp /go/bin/syncthing /gobin/blimp-syncthing
- RUN cp /go/bin/init /gobin/blimp-init
- RUN cp /go/bin/sbctl /gobin/blimp-sbctl
- RUN cp /go/bin/registry /gobin/blimp-auth
- RUN cp /go/bin/vcp /gobin/blimp-vcp
- RUN cp /go/bin/login-proxy /gobin/login-proxy
- FROM alpine
- COPY --from=builder /bin/busybox.static /bin/busybox.static
- COPY --from=builder /gobin/* /bin/
最后值得一提的是:隨著多階段構(gòu)建(multi-stage builds,請(qǐng)參見--https://docs.docker.com/develop/develop-images/multistmuage-build/)的引入,我們?nèi)缃窨梢詣?chuàng)建各種具有良好分層和較小鏡像的Dockerfile。不過,我們?cè)诖瞬⒉粫?huì)展開詳細(xì)的討論。
解決方案:使用主機(jī)卷(host volumes)
大多數(shù)語(yǔ)言都會(huì)提供一種方法來(lái)監(jiān)視程序代碼,并在代碼發(fā)生更改時(shí)自動(dòng)重新運(yùn)行。例如,nodemon就是JavaScript語(yǔ)言的一種Node自動(dòng)重啟工具(請(qǐng)參見--https://www.npmjs.com/package/nodemon)。由于主機(jī)卷可以將您電腦上的目錄,鏡像到正在運(yùn)行的容器之中,因此您在使用文本編輯器來(lái)編輯文件時(shí),各種更改將會(huì)被自動(dòng)同步到容器中,并在容器內(nèi)被立即執(zhí)行。
最初,您可能需要花點(diǎn)時(shí)間進(jìn)行前期準(zhǔn)備,之后在Docker中,您可以在1-2秒內(nèi)馬上看到代碼的更改結(jié)果。因此,我們會(huì)選擇使用主機(jī)卷將代碼直接掛載到容器中,以便以原生的方式,在包含其了運(yùn)行時(shí)依賴項(xiàng)的Docker容器中運(yùn)行自己的代碼。
錯(cuò)誤2:緩慢的主機(jī)卷
如果您使用過主機(jī)卷,那么是否已經(jīng)注意到:在Windows和Mac上讀寫文件的速度可能會(huì)非常緩慢?其實(shí),對(duì)于諸如Node.js和具有復(fù)雜依賴性的PHP應(yīng)用程序之類,需要讀寫大量文件的命令而言,這是一個(gè)已知的問題。其背后的原因是:Docker主要運(yùn)行在Windows和Mac上的VM中。而我們?cè)谶M(jìn)行主機(jī)卷的掛載時(shí),它必須經(jīng)過大量的轉(zhuǎn)換,才能使文件夾進(jìn)入容器,這有點(diǎn)類似于網(wǎng)絡(luò)文件系統(tǒng)。而此類額外的開銷,在Linux本地運(yùn)行Docker時(shí),則不會(huì)出現(xiàn)。
解決方案:放寬強(qiáng)一致性
該問題的一個(gè)關(guān)鍵原因是:文件系統(tǒng)在默認(rèn)掛載時(shí),需要保持強(qiáng)一致性。也就是說(shuō):所有特定文件的讀寫進(jìn)程都必須統(tǒng)一對(duì)于文件修改的順序,以便讓文件的內(nèi)容達(dá)成最終的一致??墒?,強(qiáng)一致性的代價(jià)非常昂貴,它需要所有文件的寫入進(jìn)程之間持續(xù)保持協(xié)調(diào),以確保它們不會(huì)干擾或破壞彼此的更改。
雖然在生產(chǎn)環(huán)境中的數(shù)據(jù)庫(kù)需要保持強(qiáng)一致性。但是在開發(fā)過程中,由于寫入進(jìn)程就是代碼文件本身,目標(biāo)就是我們的存儲(chǔ)庫(kù),因此強(qiáng)一致性就不那么必需了。那么,我們就可以考慮Docker在掛載卷時(shí),放寬強(qiáng)一致性。例如:在Docker Compose中,我們可以簡(jiǎn)單地將此cached關(guān)鍵字添加到卷掛載中,以獲得顯著的性能保證。對(duì)應(yīng)的代碼如下:
- volumes:
- - "./app:/usr/src/app/app:cached"
注意:此舉僅適合開發(fā)環(huán)境,不適合生產(chǎn)環(huán)境。
解決方案:代碼同步
另一種處置方法是設(shè)置代碼的同步。您可以使用工具偵測(cè)主機(jī)和容器之間的變化,通過復(fù)制文件來(lái)解決差異(類似于rsync),而不是掛載卷。Docker在最新的版本中內(nèi)置了用來(lái)替代卷的緩存模式--Mutagen(請(qǐng)參見--https://mutagen.io/)。此外,上文提到的Blimp則使用Syncthing(請(qǐng)參見--https://http//syncthing.net/)實(shí)現(xiàn)了類似的功能。
解決方案:不要掛載軟件包
Node之類的語(yǔ)言通常會(huì)把大部分文件操作放在packages目錄中(如node_modules)。那么,我們可以試著從卷中去除此類目錄,以顯著提高性能。下列示例是一個(gè)將代碼掛載到容器中的專屬卷,它覆蓋了node_modules目錄。
- volumes:
- - ".:/usr/src/app"
- - "/usr/src/app/node_modules"
該掛載操作會(huì)告訴Docker去使用node_modules目錄下的標(biāo)準(zhǔn)卷,以使得在npm install運(yùn)行時(shí),不再使用慢速的主機(jī)掛載方式。為了使該工作能夠正常進(jìn)行,我們應(yīng)該在容器首次啟動(dòng)時(shí),在entrypoint中執(zhí)行npm install,以安裝依賴項(xiàng),并更新node_modules目錄。具體代碼如下:
- entrypoint:
- - "sh"
- - "-c"
- - "npm install && ./node_modules/.bin/nodemon server.js"
如果您想查看并運(yùn)行上述完整的示例,請(qǐng)參考--https://kelda.io/blimp/docs/examples/#nodejs。
錯(cuò)誤3:脆弱的配置
如果您曾深入研究過代碼,您可能會(huì)發(fā)現(xiàn)Docker Compose中也充斥著各種大量復(fù)制和粘貼而來(lái)的代碼。顯然,我們需要干凈整潔的Docker Compose文件,以方便輕松地按需做出修改。
解決方案:使用各種env文件
Env文件能夠?qū)h(huán)境變量與Docker Compose主配置分開,以實(shí)現(xiàn):
- 避免將代碼泄露到git的歷史記錄中。
- 開發(fā)人員都能按需自定義設(shè)置。例如,每個(gè)開發(fā)人員都可以持有一個(gè)唯一的訪問密鑰。他們通過將配置保存在.env文件中,以實(shí)現(xiàn)不必修改已提交的docker-compose.yml文件,也不必在文件更新時(shí)處理各種沖突問題。
如果您想使用環(huán)境文件,只需添加一個(gè).env文件,或設(shè)置帶有env_file字段的顯式路徑即可(請(qǐng)參見--https://docs.docker.com/compose/environment-variables/#the-env_file-configuration-option)。
解決方案:使用替代文件
替換文件(請(qǐng)參見--https://docs.docker.com/compose/extends/)可以方便您在具有基本配置的基礎(chǔ)上,在其他文件中指定各項(xiàng)修改。該功能非常適合Docker Swarm及其YAML文件。您可以將生產(chǎn)環(huán)境的配置存儲(chǔ)在docker-compose.yml中,然后在替代文件中,指定開發(fā)所需的任何修改(例如:使用主機(jī)卷)。
解決方案:使用extends
如果您使用的是Docker Compose v2,那么就可以使用extends關(guān)鍵字,在多個(gè)位置導(dǎo)入YAML片段。例如,您可能會(huì)定義:公司里所有的服務(wù)都需要在開發(fā)的Docker Compose文件中帶有某五個(gè)特定的配置。然后您可以使用extends關(guān)鍵字將其放置到任何需要的地方,以實(shí)現(xiàn)模塊化。當(dāng)然,如果僅在YAML中執(zhí)行此項(xiàng)操作可能比較繁瑣,我們完全可以通過編程來(lái)實(shí)現(xiàn)。
雖然Compose v3刪除了對(duì)于extends關(guān)鍵字的支持。但是,您仍然可以使用YAML anchors(請(qǐng)參見--https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/)來(lái)實(shí)現(xiàn)類似的結(jié)果。
錯(cuò)誤4:亂序啟動(dòng)(Flaky Boots)
如果docker-compose出現(xiàn)了崩潰,我們能夠僅使用docker-compose restart來(lái)重啟服務(wù)嗎?其實(shí)此類問題主要與服務(wù)錯(cuò)誤的啟動(dòng)順序有關(guān)。例如,您的Web應(yīng)用可能依賴于數(shù)據(jù)庫(kù),那么在Web應(yīng)用啟動(dòng)時(shí),如果數(shù)據(jù)庫(kù)尚未準(zhǔn)備就緒,就會(huì)出現(xiàn)崩潰。
解決方案:使用depends_on
depends_on使您可以控制啟動(dòng)的順序。默認(rèn)情況下,depends_on僅判斷依賴項(xiàng)是否已經(jīng)創(chuàng)建,而不會(huì)判斷依賴項(xiàng)是否“健康”。雖然Docker Compose v2能夠支持將depends_on與運(yùn)行狀況的檢查相結(jié)合。不過,該功能也在Docker Compose v3中被去除了。當(dāng)然,您可以使用諸如wait-for-it.sh之類的腳本,來(lái)手動(dòng)實(shí)現(xiàn)類似的功能。
和上面提到的放寬強(qiáng)一致性相同,雖然Docker文檔不建議在生產(chǎn)環(huán)境中使用depends_on和wait-for-it.sh,來(lái)為容器指定特定的啟動(dòng)順序。但是對(duì)于開發(fā)而言,我們完全可以用到depends_on。
錯(cuò)誤5:資源管理不善
如果您碰到開發(fā)流程受阻,Docker無(wú)法全速運(yùn)行,或是無(wú)法平穩(wěn)地獲取運(yùn)行所需的資源,那么您可以考慮以下幾個(gè)方面:
解決方案:更改Docker Desktop的分配
Docker Desktop需要大量的RAM和CPU,尤其是在Mac和Windows的VM上。Docker Desktop的默認(rèn)配置往往不會(huì)分配足夠的RAM和CPU,因此我們通常需要調(diào)整相關(guān)的設(shè)置。在開發(fā)時(shí),我經(jīng)驗(yàn)是:為Docker分配大約8GB的RAM和4個(gè)CPU,并且在不使用Docker Desktop時(shí),及時(shí)關(guān)閉之。
解決方案:刪除未使用的資源
人們?cè)谑褂肈ocker時(shí)經(jīng)常會(huì)出現(xiàn)數(shù)百個(gè)卷與舊的容器鏡像。這在無(wú)形中浪費(fèi)了各種資源。為了釋放這些資源,我們建議通過間或運(yùn)行docker system prune的方式,以刪除當(dāng)前未使用到的所有卷、容器和網(wǎng)絡(luò)。
總結(jié)
總的說(shuō)來(lái),為了改善開發(fā)人員在使用Docker Compose時(shí)的體驗(yàn),我建議您做到如下五點(diǎn):
- 最小化容器的重建。
- 使用主機(jī)卷。
- 像對(duì)待代碼那樣,認(rèn)真配置文件,以便于維護(hù)。
- 讓啟動(dòng)更加可靠。
- 認(rèn)真分配管理資源。
此外,您還可以通過鏈接--https://kelda.io/blog/docker-volumes-for-development/,以獲悉如何設(shè)置主機(jī)卷,并加快Docker開發(fā)。
原標(biāo)題:5 Common Mistakes When Writing Docker Compose ,作者: Ethan J Jackson
【51CTO譯稿,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文譯者和出處為51CTO.com】