iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 15
0

開始發文到今天是第十五天,三十天的旅程也將完成前半部。原本打算在三十天內把這十多種 DevOps 的開源工具走過一遍,以目前的進度應該是很難達成了。從每天能寫的量來看,要在三天左右的時間介紹完一個工具還頗有難度,所以剩下的十五天,能寫到那就算那吧,可能把 Docker 相關工具包含 Kubernetes,以及 Jenkins 看完就差不多了。今天開始來看 Docker。

【應試目標能力】

Topic 702 - 容器管理 Container Management (16)

702.1 容器使用 Container Usage (7)

  • 瞭解 Docker 架構
  • 使用由 Docker Registry 取得的既存映像檔 (existing Docker image)
  • 建立 Dockerfile,並由 Dockerfile 建置映像檔
  • 上傳映像檔至 Docker registry
  • 操作並存取 Docker 容器
  • 將容器與 Docker 網路連接
  • 使用 Docker 卷宗作為容器共享及永久儲存

702.2 容器部署及編配 Container Deployment and Orchestration (5)

  • 瞭解 Docker Compose 的應用模型
  • 建立並運行 Docker Compose Files (版本3及之後)
  • 瞭解 Docker Swarm 模式的架構及功能
  • 在 Docker Swarm 中運行容器,包含服務、堆疊 (stack) 的定義以及 secrets 的使用
  • 瞭解 Kubernetes 的架構及應用模型
  • 定義並管理基於容器的 Kubernetes 應用,包含 Deployment、Service、ReplicaSet、Pod 的定義

702.3 容器設施 Container Infrastructure (4)

  • 使用 Docker Machine 設置 Docker host
  • 瞭解 Docker 網路概念,包括 overlay 網路
  • 建立並管理 Docker 網路
  • 瞭解 Docker 儲存的概念
  • 建立並管理 Docker 卷宗
  • 知道 Flocker 和 flannel
  • 瞭解服務探知 (service discovery) 的概念
  • CoreOS Container Linux、rkt 和 etcd 的基本特性知識
  • 瞭解容器虛擬化及容器映像的安全風險及如何減輕

前言

Docker、Kubernetes 這一系列與容器相關的應用管理在考試中一共有16 分,佔比是四分之一強。Docker 應該還是個在浪頭上的技術,據我所知這一陣子很紅的微服務,也是將應用程式解耦,以容器為基礎運行單元來當作服務的架構。使用容器在維運面帶來一個很重要的好處就是易於擴展,達到更好的資源利用。印象中 Docker 紅起來好像是在四、五年前,那時身旁有在玩 Docker 的朋友主要是用它來作開發環境的封裝以利可㩦性,對於 Docker 用在 production 環境似乎還存有一些疑慮,可能是穩定度或效能之類的考量。但經過這幾年,好像不再會有人去質疑 Docker 到底適不適用於 procuction,而 Kubernetes 這類容器管理工具的興起,也強化了容器在生產環境運行的可用性。開場白結束,接下來就讓我們來研究研究 Docker 吧。

市面上有許多關於 Docker 的教學和書籍,但這次介紹還是會從官方文件為主,如果有引用到其他資料會再註明。我們從官方文件的 Get Started 開始,請參考 https://docs.docker.com/get-started/。我自己覺得 Docker 的 Get Started 寫得很好,它先簡單地介紹 Docker 的概念,然後安裝 Docker,並以一個應用程式作為範例,循序漸進地介紹 Docker 生態系的重要元件及工具。強烈建議想學 Docker 的朋友直接從這份 Get Started 入手。

Docker 是什麼呢?它是一個讓開發和維員人員利用容器 (container) 來開發 (develop)、部署 (deploy) 和運行 (run) 應用程式的平台。使用 Linux 容器來部署應用程式稱作 containerization,應該可以翻成「容器化」。容器化愈來愈受歡迎,它具有以下的優點:

  1. 具彈性:即便是最複雜的應用程式也可以容器化。
  2. 輕量級:容器利用且共享主機 (host) 核心。
  3. 可抽換:應用程式運行期間亦可部署更新及升級。
  4. 可㩦性:可在本地建造 (build)、部署至雲端、到處皆可運行。
  5. 擴充性:可新增並自動分散容器複本 (replica)。
  6. 可堆疊:可在運行期間垂直地堆疊服務。

有點不知道在翻譯什麼但沒關係,通常在介紹 Docker 之前,都要先說明「容器」和「虛擬機器」有什麼不同。容器以「原生」的方式在 Linux 上運行(文件這裡用的是 natively 這個字),和其他的容器共用 host 的核心 (kernel)。基本上每個容器就只是一個單獨的行程 (process)。而虛擬機器則是透過 hypervisor 去存取 host 資源,以運行一整個作業系統,通常會耗用較多的資源。我們可以從下面的圖示看出兩者的不同。
https://ithelp.ithome.com.tw/upload/images/20181030/20111953pjUsypcCO7.png
(圖片取自 https://docs.docker.com/get-started/#images-and-containers

安裝 Docker

接下來就來安裝 Docker。Docker 目前有兩種版本,社群版本 (Docker CE,community edition) 和商用版本 (Docker EE,enterprise edition),我們安裝社群版本就可以了。一樣支援 Linux、Mac OS、Windows 等主流作業系統,這裡以 Ubuntu 為例,其他作業系統的詳細安裝方式,請參考官方文件 https://docs.docker.com/install/ 一節的說明。

這邊要稍微留意的,是在 macOS 和 Windows 中安裝。在這兩種作業系統中安裝 Docker 有兩種方式,一種是 Docker Toolbox,另一種是 Docker for Mac 或 Docker for Windows。因為 Docker 的 container 使用 Linux 核心,在 Mac 或 Windows 比較早期的方式是先安裝一個 Linux 虛擬機器,通常是用 VirtualBox,然後用它來執行 Docker,在 Mac 或 Windows 上使用 docker 相關指令,其實是對那台虛擬機上的 Docker 進行操作。Docker for Mac 和 Docker for Windows 是在比較新的 macOS 或 Windows 版本上執行 Docker 的方式(不確定是否只要作業系統在某個版本以後,印象中還需要硬體支援)。它其實還是透過 hypervisor,在 Windows 是 Microsoft Hyper-V,macOS 則是 Mac HyperKit,但底層是利用作業系統自己的虛擬化技術來支援,感覺起來比較接近原本的 Docker,不需要再透過一台 VirtualBox 虛擬機來操作。目前 Docker Toolbox 已經比較少在用了,除非機器比較老舊,不支援 Docker for Mac / Windows,否則請先嘗試以 Docker for Mac / Windows 來安裝。

我們用官方推薦,也是最簡單的方式,透過 Docker 儲存庫來安裝 Docker CE。

  1. 首先更新套件索引,並安裝一些工具讓 apt 可透過 HTTPS 使用儲存庫。
$ sudo apt-get update
$ sudo apt-get install apt-transport-https ca-certificates curl
  1. 新增 Docker GPG 金鑰。
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
  1. 新增會用到的儲存庫。
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
  1. 安裝 Docker CE
$ sudo apt-get update
$ sudo apt-get install docker-ce

安裝好之後,我們來看一下它的版本及基本訊息。

$ sudo docker --version
$ sudo docker info

接下來我們來執行 Docker 的 hello-world。它會去下載 hello-world 這個映像檔並執行它。

$ sudo docker run hello-world

請注意在一般狀況下,執行 docker 相關指令是需要 root 權限的,所以在 docker run 前我們用了 sudo,以下範例會省略 sudo

如果成功的話,會出現以下畫面:

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/

前面提到下載 hello-world 映像檔,映像檔 (image) 和容器的關係是什麼呢?其實有點像是 Vagrant 裡 box 和虛擬機器的關係,啟動 (lauch) 映像檔,就可以得到一個容器,而映像檔其實就是一個打包好、可執行的套件,裡面包括應用服務執行時所需的所有東西,像是程式碼、運行程式庫、設置檔等等。容器是映像檔的一個運行實例 (runtime instance) ,此時映像檔會被載入記憶體,並成為一個行程 (process)。

知道了 image 和 container 的差異,我們來看看幾個命令,首是先查看下載了那些映像檔。

$ docker image ls 
或者
$ docker images

結果如下:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              4ab4c602aa5e        7 weeks ago         1.84kB

查看目前系統中有那些容器:

$ docker container ls
或者
$ docker ps

不過這裡執行 docker container ls 不會看到剛才執行 hello-world 映像檔所產生的容器,因為該容器一執行後就會停止,而 docker container ls 不會列出停止的容器,必須加上 -a (--all) 旗標。執行 docker container ls -a 的結果如下:

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
e05b1d763e3f        hello-world         "/hello"            48 seconds ago      Exited (0) 47 seconds ago                       competent_cori

我將 Docker 指令分為兩類,一類是管理類的指令,另一類是一般指令。管理類的指令用來管理 docker 物件,基本格式為 docker <object> <action>,下面是物件管理的指令。

$ docker config
$ docker image
$ docker container
$ docker network
$ docker node
$ docker plugin
$ docker secret
$ docker service
$ docker stack
$ docker swarm
$ docker system
$ docker trust
$ docker volume

使用 docker --help 來查看可用的指令,每個指令皆可以 --help 旗標獲得更詳細的資訊。管理類指令在執行時通常會加上動作,例如 docker image ls。其中某些操作會有另一個一般指令可以達到相同的效果,例如上面提到的兩個例子,dokcer image lsdocker imagesdokcer container lsdocker ps 等等。

在容器中部署應用程式

Get Started 的第一部分到此為止,接下來會以一個簡單的應用程式作為範例,並依下列順序逐漸擴充:

  1. 以單一容器來執行應用程式。
  2. 擴展服務,讓應用程式在多個容器實例上運行。
  3. 將應用程式分散到叢集 (cluster) 上。
  4. 新增後端資料庫服務,讓應用程式成為「服務堆疊」的架構
  5. 將應用程式部署至雲端。

文件中將這個應用程式的開發分為三個階層,最底層是容器,中間是服務,最上層是堆疊,接下來依序說明這三層,但首先介紹這個應用程式本身在做些什麼。

這個應用程式是用 Python 的 Flask 框架建立一個簡單的網頁伺服器,並且連接一個 Redis 資料庫(這個資料庫目前還沒有)。以前要建立這樣的應用程式,必須先安裝 Python,確保開發環境能讓程式碼如預期般的執行,而且和生產環境保持一致性。若使用 Docker 來開發,只要選擇一個有Python 運行的映像檔,並將它連同應用程式源碼一併打包即可,這個打包好的映像檔應該在開發環境、生產環境都可以執行,而且所有需要的相依檔案都包含在這個映像檔之中,就不會有那種「在我的電腦可以跑」的情況發生。那要怎麼把這些東西打包成一個映像檔呢?這時 Dockerfile 就派上用場了。下面是 Get Started 這一節所提供的 Dockerfile。跟 Vagrant 的情況一樣,這個檔案本身就叫 Dockerfile

# Use an official Python runtime as a parent image
FROM python:2.7-slim

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

我們透過註解來看一下這個 Dockerfile 在做什麼。首先是我們要打包的映像檔會用到的基礎映像檔,這裡用的是 python:2.7-slim,接下來是設定工作目錄,大概就是進入這個容器後要 cd (change directory) 到 /app 的意思。然後我們要把一些檔案拷貝到 /app 下,並使用 pip install 安裝一些所需的套件,並揭露 (expose) 容器的 port 80,不過實際上要連線到這個服務時還是要綁定本機的某個 port。最後是設定環境變數,並執行 app.py 這支程式。

接下來還有兩個需要的檔案,第一個是 requirements.txt,記錄應用程式所需的 Python 套件:

Flask
Redis

接下來是應用程式本身,檔名為 app.py

from flask import Flask
from redis import Redis, RedisError
import os
import socket

# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)

app = Flask(__name__)

@app.route("/")
def hello():
    try:
        visits = redis.incr("counter")
    except RedisError:
        visits = "<i>cannot connect to Redis, counter disabled</i>"

    html = "<h3>Hello {name}!</h3>" \
           "<b>Hostname:</b> {hostname}<br/>" \
           "<b>Visits:</b> {visits}"
    return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

Dockerfilerequirements.txtapp.py 放在同一個目錄中,並進入該目錄,就可以來建立 (build) 所需的映像檔了。

$ docker build -t friendlyhello .

最後的 . 是指將所在的目錄作為 build context,build context 不知道中文怎麼翻比較好,但之後會介紹。-t 的旗標用來幫這個映像檔命名。這裡 t 其實是 tag 的意思,但它的參數 friendlyhello 比較類似是映像檔的名稱。建立映像檔後可以用前面介紹過的 docker images 去看有沒有建立成功,發現剛才 -t 參數 friendlyhello 出現在 REPOSITORY 這個欄位,TAG 欄位出現的反而是 latest,這裡請稍微確認一下。

接下來執行下面這個指令,它會執行剛才建立的映像檔 friendlyhello 並產生一個容器。-p 4000:80 參數用來將容器的 80 port 對應到本機的 4000 port,這樣才能從外部連接到這個容器裡的應用程式。

$ docker run -p 4000:80 friendlyhello

輸出如下:

 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

這個容器目前是運作在前景 (foreground) 的。打開瀏覽器,在網址列輸入 http://localhost:4000,沒問題的話就可以看到以下的畫面,表示應用程式已成功在容器中運行。

回到終端機,會看到畫面上多了剛才以瀏覽器讀取網站首頁的記錄。現在依提示鍵入 Ctrl - C 以停止容器執行。文件指出在 Windows 環境中這麼做並不會停止容器,因此要用 docker container stop 停止此容器,待會會介紹這個指令。

再執行一次應用程式,但是讓它以「分離模式」(detached mode) 在背景中執行。輸入以下指令,會輸出容器的 ID 並返回命令提示字元。

$ docker run -d -p 4000:80 friendlyhello

docker container lsdocker ps 確認目前執行中的容器,結果如下:

CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS                  NAMES
2c826b5a8e36        friendlyhello       "python app.py"     About a minute ago   Up About a minute   0.0.0.0:4000->80/tcp   agitated_heyrovsky

因為剛才 docker run 的指令沒有給予產生的容器名稱,Docker 會隨意給予此容器一個名稱,請參照 NAMES 欄位。現在以 docker container stop <container> 指令來停止此容器,最後的參數可輸入 container ID 或 name。以上述例子而言,container ID 是 2c826b5a8e36,名稱則是 agitated_heyrovsky,若使用 container ID,可使用前面若干字元,長度讓 Docker 能唯一辨識即可。例如:

$ docker container stop 2c82
或者
$ docker container stop agitated_heyrovsky

docker container stop 會優雅停止 (gracefully stop) 容器,若要強制關閉 (force shutdown) 容器,可用 docker container kill <container> 指令。

此外在列出 (ls) 映像檔或容器的指令時,若使用 -q (--quiet) 旗標 ,只會列出物件 ID,可搭配 rmkill 這類需要物件 ID 作為參數的指令使用,例如要刪除所有容器,可用這個指令:

$ docker container rm $(docker container ls -a -q)

因為 docker container rm 等於 docker rmdocker container ls 等於 docker ps,所以上面的指令也可以寫成

$ docker rm $(docker ps -aq)

請注意因為沒有加上 -f 旗標,所以不會刪除運作中的容器,要是有容器還在運行狀態中會跳出錯誤訊息,但其他已停止的容器仍會被刪除。

要查看某個物件的資訊,可以用 docker inspect <object> 指令。這裡的 object 可以是映像檔或容器的 ID。

今天介紹了 Docker 的基本概念、安裝方法,接下來跟著官方文件的 Get Started,瞭解使用 Docker 作為應用程式服務可分為三種層級。在第一個 container 容器層級,利用 Dockerfile 建立了自己的映像檔,並在單個容器中執行應用程式。明天會先介紹如何分享建立好的映像檔,並繼續跟著文件進入 service 服務層級的實作。


上一篇
[Day 14] Ansible (5)
下一篇
[Day 16] Docker (2)
系列文
30 天準備 LPI DevOps Tools Engineer 證照30

尚未有邦友留言

立即登入留言