經驗之談:八種Docker容器開發模式

CSDN發表於2014-11-02

Docker現在成了我最喜歡的工具,在本文中,我將概述一些在我使用Docker過程中反覆出現的模式。我不期待它們能給你帶來多少驚喜,但我希望這些能對你有用,我非常願意與你交流在使用Docker過程中碰到的模式。

我所有Docker實驗的基礎是保持volume狀態不變,以便Docker容器在沒有資料丟失的前提下任意重構。

下面所有的Dockerfiles例子都集中在:建立容器在其本身可以隨時更換的地方,而無需考慮其它。

1. The Shared Base Container(s)

Docker鼓勵“繼承”,這應用也很自然——這是高效使用Docker的一個基本方式,不僅由於它有助於減少建立新容器的時間,Docker優點多多,它會cache中間步驟,但也容易在不明確的情況下,失去分享機會。

很顯然,在將我的各種容器遷移到Docker上時,首先要面對的是多個步驟。

對於多數想要隨處部署的專案來說所,要建立多個容器,尤其是在這個專案需要長程式,或者需要特定包的情況,所以我要執行的容器也變得越來越多。

重要的是為了讓mybase環境完全自由支配,我正考慮試圖在Docker上執行“所有一切”(包括我依賴幾個桌面app)。

所以我很快開始提取我的基本設定到base容器。這是我當前的“devbase”Dockerfile:

    FROM debian:wheezy
    RUN apt-get update
    RUN apt-get -y install ruby ruby-dev build-essential git
    RUN apt-get install -y libopenssl-ruby libxslt-dev libxml2-dev

    # For debugging
    RUN apt-get install -y gdb strace

    # Set up my user
    RUN useradd vidarh -u 1000 -s /bin/bash --no-create-home

    RUN gem install -n /usr/bin bundler
    RUN gem install -n /usr/bin rake

    WORKDIR /home/vidarh/
    ENV HOME /home/vidarh

    VOLUME ["/home"]
    USER vidarh
    EXPOSE 8080

這裡沒有什麼需要特別說明的地方——它安裝一些需要隨時可用的特定工具。這些可能會對大多數人來說是不同的。值得注意的是如果/當你重建一個容器的時候,你需要指定一個特定的標籤來避免意外。

使用預設埠8080,因為這是我釋出web app的埠,這也是我用這些容器的目的。

它為我新增了一個使用者,並且不會建立一個/ home目錄。我從宿主機繫結掛載了一個共享資料夾/ home,這就引出了下一個模式。

2. The Shared Volume Dev Container

我所有的dev容器與宿主機分享至少一個volume: / home,這是為了便於開發。對於許多app,在開發模式中,使用基於file-system-change的code-reloader執行,這樣一來容器內封裝了OS / distro-level的依賴,並在初始環境中幫助驗證app-as-bundled工作,而不需要讓我每次在程式碼改變時重啟/重建VM。

至於其他,我只需要重啟(而不是重建)容器來應對程式碼的更改。

對於test/staging和production容器,大多數情況下不通過volume共享程式碼,轉而使用“ADD”來增添程式碼到Docker容器中。

這是我的“homepage”的dev容器的Dockerfile,例如,包含我的個人wiki,存在於“devbase”容器中的 /home下,以下展示瞭如何使用共享的base容器和/home卷:

    FROM vidarh/devbase
    WORKDIR /home/vidarh/src/repos/homepage
    ENTRYPOINT bin/homepage web

以下是dev-version的部落格:

    FROM vidarh/devbase

    WORKDIR /
    USER root

    # For Graphivz integration
    RUN apt-get update
    RUN apt-get -y install graphviz xsltproc imagemagick

    USER vidarh
    WORKDIR /home/vidarh/src/repos/hokstad-com
    ENTRYPOINT bundle exec rackup -p 8080

因為他們從一個共享的庫中取程式碼,並且基於一個共享的base容器,這些容器通常當我新增/修改/刪除依賴項時會極其迅速重建。

即便如此也有一些地方是我非常願意改善,儘管上面的base是輕量級的,他們大多數在這些容器仍未使用。由於Docker使用copy-on-write覆蓋,這不會導致一個巨大的開銷,但它仍然意味著我沒有做到最小的資源消耗,或者說最小化attack或error的機率。

3. The Dev Tools Container

這可能對我們這些喜歡依靠ssh寫程式碼的人很有吸引力,但是對IDE人群則小一點。對我來說,關於以上設定更大的 一個好處,是它讓我在開發應用程式中,能夠將編輯和測試執行程式碼的工作分離開來。

過去dev-systems對我來說一件煩人的事,是dev和production依賴項以及開發工具依賴項容易混淆,很容易產生非法的依賴項。

雖然有很多方法解決這個,比如通過定期的測試部署,但我更偏愛下面的解決方案,因為可以在第一時間防止問題的發生:

我有一個單獨的容器包含Emacs的installation以及其他各種我喜歡的工具,我仍然試圖保持sparse,但關鍵是我的screen session可以執行在這個容器中,再加上我膝上型電腦上的“autossh”,這個連線幾乎一直保持,在那裡我可以編輯程式碼,並且和我的其他dev容器實時共享。如下:

FROM vidarh/devbase
RUN apt-get update
RUN apt-get -y install openssh-server emacs23-nox htop screen

# For debugging
RUN apt-get -y install sudo wget curl telnet tcpdump

# For 32-bit experiments
RUN apt-get -y install gcc-multilib 

# Man pages and "most" viewer:
RUN apt-get install -y man most

RUN mkdir /var/run/sshd
ENTRYPOINT /usr/sbin/sshd -D

VOLUME ["/home"]
EXPOSE 22
EXPOSE 8080

結合共享“/ home“,已經足夠讓ssh的接入了,並且被證明能滿足我的需要。

4. The Test In A Different Environment containers

我喜歡Docker的一個原因,是它可以讓我在不同的環境中測試我的程式碼。例如,當我升級Ruby編譯器到1.9時,我可以生成一個Dockerfile,派生一個1.8的環境。

FROM vidarh/devbase
RUN apt-get update
RUN apt-get -y install ruby1.8 git ruby1.8-dev

當然你可以用rbenv等達到類似的效果。但我總是覺得這些工具很討厭,因為我喜歡儘可能多地用distro-packages部署,不僅僅是因為如果這項工作的順利進行,能使其他人更容易地使用我的程式碼。

當擁有一個Docker容器,我需要一個不同的環境時,我僅需要“docker run”一下,幾分鐘便能很好的解決這個問題。

當然,我也可以使用虛擬機器來達到目的,但使用Docker更省時間。

5. The Build Container

這些天我寫的程式碼大部分都是解釋性語言,但還是有一些代價高昂的“build”步驟,我並不願意每次都去執行它們。

一個例子是為Ruby應用程式執行“bundler”。Bundler 為Rubygems更新被快取的依賴,並且需要時間來執行一個更大的app。

經常需要在應用程式執行時不必要的依賴項。例如安裝依賴本地擴充套件gems的通常還需要很多包——通常沒有記錄——通過新增所有build-essential和它的依賴項就輕鬆啟動。同時,你可以預先讓bundler做所有的工作,我真的不想在主機環境中執行它,因為這可能與我部署的容器不相容。

一個解決方案是建立一個build容器。如果依賴項不同的話,你可以建立分別的Dockerfile,或者你可以重用主app Dockerfile以及重寫命令執行你所需的build commands。Dockerfile如下:

FROM myapp
RUN apt-get update
RUN apt-get install -y build-essential [assorted dev packages for libraries]
VOLUME ["/build"]
WORKDIR /build
CMD ["bundler", "install","--path","vendor","--standalone"]

然後每當有依賴更新時,都可以執行上面的程式碼,同時將build/source目錄掛載在容器的”/build”路徑下。

6. The Installation Container

這不是我擅長的,但是真的值得提及。優秀的nsenter和docker-enter工具在安裝時有一個選項,對於現在流行的curl | bash模式是一個很大的進步,它通過提供一個Docker容器實現“Build Container”模式。

這是Dockerfile的最後部分,下載並構建一個nsenter的合適版本:

ADD installer /installer
CMD /installer

“installer”如下:

#!/bin/sh
if mountpoint -q /target; then
       echo "Installing nsenter to /target"
       cp /nsenter /target
       echo "Installing docker-enter to /target"
       cp /docker-enter /target
else
       echo "/target is not a mountpoint."
       echo "You can either:"
       echo "- re-run this container with -v /usr/local/bin:/target"
       echo "- extract the nsenter binary (located at /nsenter)"
fi

雖然可能還有惡意攻擊者試圖利用容器潛在的特權升級問題,但是attack surface至少顯著變小。

這種模式能吸引大多數人,是因為這種模式能避免開發人員在安裝指令碼時偶爾犯的非常危險的錯誤。

7. The Default-Service-In-A-Box Containers

當我認真對待一個app,並且相對快速的準備一個合適的容器來處理資料庫等,對於這些專案,我覺得難能可貴的是已經有一系列的“基本的”基礎設施容器,只做適當的調整就可以滿足我的需求。

當然你也可以通過“docker run”得到“主要的”部分,在Docker索引裡有諸多替代品,但我喜歡首先檢查它們,找出如何處理資料,然後我將修改版本新增到自己的“library”。

例如Beanstalkd:

FROM debian:wheezy
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get -q update
RUN apt-get -y install build-essential
ADD <a href="http://github.com/kr/beanstalkd/archive/v1.9.tar.gz">http://github.com/kr/beanstalkd/archive/v1.9.tar.gz</a> /tmp/
RUN cd /tmp && tar zxvf v1.9.tar.gz
RUN cd /tmp/beanstalkd-1.9/ && make
RUN cp /tmp/beanstalkd-1.9/beanstalkd /usr/local/bin/
EXPOSE 11300
CMD ["/usr/local/bin/beanstalkd","-n"]

8. The Infrastructure / Glue Containers

許多這些模式專注於開發環境(這意味著有production 環境有待討論),但有一個大類別缺失:

容器其目的是將你的環境組合起來成為一個整體,這是目前為止對我來說有待進一步研究的領域,但我將提到一個特殊的例子:

為了輕鬆地訪問我的容器,我有一個小的haproxy容器。我有一個萬用字元DNS條目指向我的主伺服器,和一個iptable入口開放訪問我的haproxy容器。Dockerfile沒什麼特別的:

FROM debian:wheezy
ADD wheezy-backports.list /etc/apt/sources.list.d/
RUN apt-get update
RUN apt-get -y install haproxy
ADD haproxy.cfg /etc/haproxy/haproxy.cfg
CMD ["haproxy", "-db", "-f", "/etc/haproxy/haproxy.cfg"]
EXPOSE 80
EXPOSE 443

這裡有趣的是haproxy.cfg

backend test
    acl authok http_auth(adminusers)
    http-request auth realm Hokstad if !authok
    server s1 192.168.0.44:8084

如果我想要特別一點,我會部署類似 AirBnB’s Synapse ,但這已經超出了我的需求。

在工作時擴大容器的規模,目的是讓部署應用程式簡單便捷,就像我正在過渡到一個完整的面向Docker的私有云系統。

相關文章