Docker 第一課 - 構建你的容器

周楷雯Kevin發表於2017-03-17

前置閱讀

什麼是 Docker?

讓部署 Server 像手機安裝 App 一樣簡單

過去當你寫完了 Server 想要部署到伺服器上,需要在伺服器上配置一套執行環境,一旦伺服器上環境配置失敗,還可能出現整個服務掛掉的情況。

Docker 正是用 Linux 裡叫做 “容器” 的技術實現了這個特性,通過容器把你的服務需要的環境和程式碼都打包進去,保證可以正常執行,當你需要部署到 Server 上的時候,只需要伺服器上下載這個容器,然後執行就可以了。

編譯,打包成容器,分發

利用容器的這個原理,各種服務,部落格,網站,論壇,都可以使用由別人製作好容器,你再安裝到自己的伺服器上,容器之間互不干擾,乾淨整潔,大大簡化了維護和配置成本。

什麼是 “容器” ?

技術上來講,容器的實現比較複雜也是一種虛擬化技術,但是概念非常好理解。

拿 iOS 的 App 打個比方,一個 iOS App 要執行起來,需要四部分

  • iOS 核心(Darwin)
  • 系統提供的 Frameworks
  • 各種依賴的第三方 Frameworks
  • 程式自己

當你編譯出來一個 App 的時候,這個 App 相當於一個 容器 它包含了 各種依賴的第三方 Frameworks程式自己,而 iPhone 相當於你程式的宿主,提供了 iOS 核心系統提供的 Frameworks.

在 Linux 上,一個程式能跑起來,需要三部分

  • Linux 核心
  • 程式依賴的其他程式
  • 程式自己

那麼一個容器裡,包含了 程式依賴的其他程式程式自己,核心則共用宿主的。

Linux 的容器環境和 iOS 執行 App 的 Sandbox 沙盒技術也非常相似,容器內無法直接訪問外部,但外部則可以訪問容器內。

安裝 Docker

通過 Docker 官網 的 Get Docker 指南,可以輕鬆把 Docker 裝在系統上,以下我將會以 Mac 為例。

Docker Store 裡下載 Mac 版本的安裝包

Docker 第一課 - 構建你的容器
20170317148968568827600.png

Mac 的核心是不支援容器技術的,因此 Mac 版本的 Docker 帶了一個微型的 Linux 髮型版,通過虛擬化技術執行在你的 Mac 上。

安裝好之後執行,你的右上角就會出現一個小鯨魚

Docker 第一課 - 構建你的容器
20170317148968575625266.png

此時終端也可以執行 docker 命令了

➜  ~ docker -v
Docker version 17.03.0-ce, build 60ccb22複製程式碼

如何構建一個容器?

構建容器前需要先構建容器映象,分為兩步

  • 構建一個能夠執行你的程式的環境
  • 把程式放進去

那麼第一步,就是從 Dockerfile 開始

編寫 Dockerfile

Dockerfile 是容器映象的描述檔案,告訴 Docker 如何建立一個容器映象。我們之前使用的 Vapor 框架也提供了一個可以執行 Vapor 的容器映象,一起來看看它的 Dockerfile 是怎麼寫的

FROM vapor/vapor:1.0-xenial

# Check if we should install mysql header files to the container (Defualt: false)
ARG INSTALL_MYSQL=false
RUN if [ ${INSTALL_MYSQL} = true ]; then \
    apt-get update && \
    apt-get install -y libmysqlclient20 libmysqlclient-dev && \
    rm -r /var/lib/apt/lists/* \
 ;fi

# Check if we should install postgres header files to the container (Defualt: false)
ARG INSTALL_PGSQL=false
RUN if [ ${INSTALL_PGSQL} = true ]; then \
      apt-get update && \
      apt-get install -y libpq-dev && \
      rm -r /var/lib/apt/lists/* \
;fi

# Check if we should install sqlite header files to the container (Defualt: false)
ARG INSTALL_SQLITE=false
RUN if [ ${INSTALL_SQLITE} = true ]; then \
      apt-get update && \
      apt-get install -y libsqlite3-dev && \
      rm -r /var/lib/apt/lists/* \
;fi


# Set work dir to /vapor
WORKDIR /vapor

EXPOSE 8080複製程式碼

這一大坨其實就是四步,也是構建一個容器映象最基本的四步

第一步,設定基礎容器

FROM vapor/vapor:1.0-xenial複製程式碼

意思是基於 vapor/vapor:1.0-xenial 這個容器映象來構建,像程式設計中類的繼承關係一樣,容器映象也可以繼承,這樣的好處是每個容器映象只需要維護好自己的那部分,分發和保持健壯性更方便。

Docker 在構建時,會自動去本地和 Docker 雲端的倉庫裡尋找被 FROM 指定的映象。

第二步,安裝必要的依賴

# Check if we should install mysql header files to the container (Defualt: false)
ARG INSTALL_MYSQL=false
RUN if [ ${INSTALL_MYSQL} = true ]; then \
    apt-get update && \
    apt-get install -y libmysqlclient20 libmysqlclient-dev && \
    rm -r /var/lib/apt/lists/* \
 ;fi

#### Check if we should install postgres header files to the container (Defualt: false)
ARG INSTALL_PGSQL=false
RUN if [ ${INSTALL_PGSQL} = true ]; then \
      apt-get update && \
      apt-get install -y libpq-dev && \
      rm -r /var/lib/apt/lists/* \
;fi

# Check if we should install sqlite header files to the container (Defualt: false)
ARG INSTALL_SQLITE=false
RUN if [ ${INSTALL_SQLITE} = true ]; then \
      apt-get update && \
      apt-get install -y libsqlite3-dev && \
      rm -r /var/lib/apt/lists/* \
;fi複製程式碼

父級的容器映象必然缺少部分你所需要的環境,因此你可以在這一步通過 RUN 命令來進行一些依賴安裝。

第三步,設定工作目錄

# Set work dir to /vapor
WORKDIR /vapor複製程式碼

我們將容器內的 /vapor 目錄作為工作目錄,意味著如果你 ssh 進入這個容器,會直接進入到這個目錄,容器將把這個資料夾當作各種命令執行的根目錄。

第四部,設定訪問埠

EXPOSE 8080複製程式碼

意味著容器會開啟 8080 埠的外部訪問,宿主機器執行這個容器的時候,可以使用 -p xxxx:8080 的方式,將宿主機器的埠和容器的埠連線起來。

這樣當使用者在網路上訪問宿主機器的 xxxx 埠的時候,就會變成通過 8080 埠訪問容器。

此時,如果容器內的 Server 監聽的是 8080 埠,那麼伺服器就可以接收到請求啦。

構建自己的 Dockerfile

首先,在終端進入到 vapor 專案根目錄,建立一個 Dockerfile。

為了跑起來 Vapor,我需要按照以下思路構建一個 Dockerfile

  • 選一個有 Swift 執行環境的父容器映象
  • 將 Swift 專案原始碼拷貝到工作目錄
  • 配置編譯專案的環境並編譯
  • 設定訪問埠 8000
# 選一個有 Swift 執行環境的容器映象 vapor/vapor:latest 
FROM vapor/vapor:latest

WORKDIR /vapor

# 將 Swift 專案原始碼拷貝到工作目錄
ADD . /vapor

# 配置編譯專案的環境並編譯
RUN apt-get update &&  apt-get install -y openssl libssl-dev  --no-install-recommends && \
    vapor build && \
    apt-get remove -y libssl-dev && \
    rm -rf /var/lib/apt/lists/*

# 設定訪問埠 8000
EXPOSE 8000複製程式碼

現在執行 docker build . --tag sudongpo_image Docker 就會先根據 FROM 引用的映象啟動一個容器作為編譯環境,然後逐條執行當前目錄裡的 Dockerfile 裡的命令,執行完畢後,會輸出一行資訊

Successfully built sudongpo複製程式碼

在執行完 Dockerfile 退出的時候,Docker 會把當時的容器狀態進行快照,變成一個不可改動的東西,叫做 容器映象,並命名為 sudongpo_image

容器映象是什麼?

那麼映象和容器什麼關係呢?

容器映象相當於手機上的 ROM,是隻讀的部分,當容器建立的時候,這部分會變成容器的 Base Layer ,就像是一個鎖死的 Sketch 圖層,一個灰色的圓。

Docker 第一課 - 構建你的容器
20170317148969110166553.png

同時,Docker 會為容器建立一個可讀寫層,就像覆蓋在被鎖死的圖層上的另外一個圖層

Docker 第一課 - 構建你的容器
20170317148969122939254.png

在容器執行的時候,雖然你看起來可以修改任何東西,使得圖片變成了下圖的模樣

Docker 第一課 - 構建你的容器
2017031714896913319798.png

但本質上來講,底層灰色的圓圈,沒有被改動,只是表現層,即執行態的層面被改動了

Docker 第一課 - 構建你的容器
20170317148969146873539.png

這些變動會被 Docker 儲存在 /var/lib/docker/aufs/diff/xxxxx_hash/ 裡(根據 Docker 的配置不同,路徑也會有所不同)。

我們往下看,來繼續梳理兩者的關係。

現在,你可以使用 docker run --name sudongpo -p 8000:8000 sudongpo_image vapor run 來建立並執行一個容器。

被隱藏的細節

docker run 其實是將 docker createdocker start 兩個命令合併在了一起。

這行命令做了以下幾件事

  • 使用 sudongpo_image 這個容器映象作為基礎執行環境建立容器
  • --name sudongpo 將容器命名為 sudongpo
  • -p 8000:8000 對映宿主的 8000 埠到容器的 8000
  • vapor run 當容器啟動後,在工作目錄執行 vapor run 命令
  • -p 8000:8000 vapor run 作為容器的配置項儲存起來,每次啟動容器的時候都按照這個配置進行啟動
  • 執行容器

自此,你可以使用 docker start sudongpo 來開啟這個容器,或者使用 docker stop sudongpo 來關閉這個容器,容器啟動的時候,會自動完成埠對映,並執行 vapor run

在下一篇博文中,我們將把這個映象部署到你的伺服器上。

擴充閱讀

10張圖帶你深入理解Docker容器和映象

相關文章