Python 的函式應用 – First-Class Function in Python

首先很快說明一下什麼是First-Class object(一級物件):

  • 可以被當參數傳遞的
  • 可以被回傳的
  • 可以賦值給變數
  • 可以以資料結構儲存

一級函式(First Class Function)

Python的物件型別如: int、float、string、tuple、list都屬於一級物件,function也是其中之一

高階函式(Higher Order Function)

  • 可以接收function當參數,或
  • 回傳的type是function

map, filter

docstring

docstring是程式碼不是註解,用意是替你的方法加上一段說明,根據PEP257的定義,加在function第一行的字串被視為docstring

另外一種加說明的的方式,根據PEP3107所定義的function annotation

Read more “Python 的函式應用 – First-Class Function in Python”

發表留言

初始Django專案並客製化設定檔 – Django Setup With Split Settings

Django Setup 專案初始化

假設我們想要建立一個django專案(1.9版本,linux為例),命名為mysite,並且不想要將特定的設定上傳,可以如何做:

clone你的倉庫(myrepo)至本地

建立虛擬環境

可以透過指令或IDE,我習慣會將環境命名為venv,然後放在專案目錄下

安裝django並開始一個專案

初始化django專案

別忘了把你安裝的套件加到mysite/requirements.txt

這時候你的tree應該長這樣

Read more “初始Django專案並客製化設定檔 – Django Setup With Split Settings”

發表留言

Python函式的參數設計 – Python Function Parameters

parameter跟argument的差異

基本上這兩者是一樣的,不過使用在不同的情境下。如果混用了其實是沒關係,可能也沒人會在意

不過值得一提的是以上這個範例的兩個參數在Module ScopeFunction Scope中都是指向同一個記憶體位置


可迭代物件的拆解機制(unpacking)

可迭代的物件都能拆解,很適合用在參數傳遞(主要使用在有序物件上)

超簡單實現swap

傳統的作法

unpacking的作法

使用***(3.5以上適用)

unpacking範例

巢狀的unpacking

Read more “Python函式的參數設計 – Python Function Parameters”

發表留言

以Python實作資料結構 – Data Structure Implements in Python

以Python實作資料結構

tags: data-structure, python

TOC

簡介

什麼是資料結構?為什麼要使用資料結構?

是電腦中儲存、組織資料的方式,可以讓我們有效地儲存資料,並讓所有運算能最有效率地完成

演算法的運行時間是根據資料結構決定的,所以使用適當的資料結構來降低演算法的時間複雜度,如:

  • 最短路徑演算法若無適當的資料結構,運行時間是O(N^2),使用(heap/priority queue)可以大幅降低運行時間至O(N*logN)

抽象資料型態 Abstract Data Types

簡單而言,ADT是針對資料結構的「規範」或「描述」,像是物件導向語言裡面的interface,但不會實作細節

舉例堆疊的ADT描述:

  • push(): 插入元素 item 至堆疊頂端
  • pop(): 移除並回傳堆疊頂端的元素
  • peek(): 看堆疊頂端的資料而不取出
  • size(): 看堆疊的長度

ADT跟資料結構的關係

每個ADT在底層都有相對應的資料結構去實作ADT裡定義過的行為(method)

ADT Data Structures
Stack array, linked list
Queue array, linked list
Priority Queue heap
Dictionary/Hashmap array

時間複雜度 Big O notation

描述演算法的效率(複雜度),舉例來說,A宅想要分享他的D槽給B宅,有以下幾種做法:

  1. 台北騎車到屏東B宅家
  2. 用網路傳輸,不考慮被FBI攔截的情況
1GB 1TB 500TB
騎車運送硬碟 600 min 600 min 600 min
網路傳輸 3 min 3072 min 1536000 min

從上表來看,騎車這個選項雖然聽起來很蠢,但不管硬碟有多大,都能確保10個小時內可以送達—— O(1);至於網路傳輸隨著檔案越大,所需的時間也越長 —— O(N);從這裡就可以看出常數時間(constant time)和線性時間(linear time)的差別對效率的影響有多大了

在表現複雜度函數的時候,有幾個通用的規則:

  • 多個步驟用加法: O(a+b)

  • 省略常數: ~~O(3n)~~ O(n)

  • 不同的input用不同的變數表示: ~~O(N^2)~~ O(a*b)

  • 省略影響不大的變數: ~~O(n+n^2)~~ O(n^2)

陣列 Array

物件或值的集合,每個物件或值可以被陣列的索引(index, key)識別

  • 索引從0開始
  • 因為有索引,我們可以對陣列做隨機存取(Random Access)

優點:

  • 隨機存取不用搜尋就能訪問陣列當中所有值,執行速度快O(1)
  • 不會因為鏈結斷裂而遺失資料
  • 循序存取快

缺點:

  • 重建或插入陣列須要逐一複製裏頭的值,時間複雜度是O(N)
  • 編譯的時候必須事先知道陣列的大小,這讓陣列這個資料結構不夠動態(dynamic)
  • 通常陣列只能存同一種型別
  • 不支援連結串列的共享

Implements

行為 big O
search 搜尋 O(1)
insert 插入第一項 O(N)
append 插入最後一項 O(1)
remove 移除第一項 O(N)
removeLast 移除最後一項 O(1)

以Python實作

random indexing: O(1)

linear search: O(n)

連結串列 Linked List & 雙向連結串列 Double Linked List

  • 節點包含datareferenced object
  • 連結的方式是節點(node)記住其他節點的參考(reference)
  • 最後一個節點的參考是NULL

優點

  • 各節點型態、記憶體大小不用相同
  • 動態佔用的記憶體,不須事先宣告大小
  • 插入、刪除快O(1)

缺點

  • 不支援隨機存取,只能循序存取(sequencial access),時間複雜度為O(N)
  • 須額外空間儲存其他節點的參考
  • 可靠性較差,連結斷裂容易遺失資料
  • 難以向前(backward)訪問,可以用雙向連結串列來處理,不過會多佔用記憶體空間

Implements

行為 big O
search 搜尋 O(N)
insert 插入第一項 O(1)
append 插入最後一項 O(N)
remove 移除第一項 O(1)
removeLast 移除最後一項 O(N)

註:連結串列沒有index,處理插入或移除第N項會需要先循序找到插入/移除位置,因此會需要O(N)的時間

以Python實作

以下的代碼是我實作的範例,有錯誤煩請指正。

主要概念是實作__getitem__來循序存取(indexing),另外Double Linked List支援反向存取,故訪問lst[0]lst[-1]皆可以達成O(1)的時間複雜度

執行結果請參考travishen/gist/linked-list.md

Linked List現實中的應用

  1. 低級別的內存管理(Low Level Memory Management),以C語言為例:
  • malloc()free(): 見Heap Management
  • chart * chart_ptr = (chart*)malloc(30);: 取得30byte的heap memory
  1. 許多Windows的應用程式:工具列視窗切換、PhotoViewer
  2. 區塊鏈技術

image
[圖片來源]

堆疊 Stack

Implements

行為 big O
push 將資料放入堆疊的頂端 O(1)
pop 回傳堆疊頂端資料 O(1)
peek 看堆疊頂端的資料而不取出 O(1)

應用

  • call stack + stack memory
  • 深度優先搜尋演算法(Depth-First-Search)
  • 尤拉迴路(Eulerian Circuit)
  • 瀏覽器回上一頁
  • PhotoShop上一步(undo)

註:任何遞迴(recursion)形式的演算法,都可以用Stack改寫,例如DFS。不過就算我們使用遞迴寫法,程式最終被parsing還是Stack

Stack memory vs Heap memory

可參考Stack vs. Heap

stack memory heap memory
有限的記憶體配置空間 記憶體配置空間較大
存活時間規律可預測的 存活時間不規律不可預測的
CPU自動管理空間(GC) 使用者自主管理空間
區域變數宣告的空間不能更動 物件的值可以變動,如realloc()

另外ptt有針對兩者佔用記憶體大小的討論stack v.s. heap sizes

以Python實作

Using Lists as Stacks

佇列 Queue

  • 佇列是一種抽象資料型態,特性是先進先出(FIFO, first in first out)
  • 在高階程式語言,容易用array、linked list來實作

應用

  • 多個程序的資源共享,例如CPU排程
  • 非同步任務佇列,例如I/O Buffer
  • 廣度優先搜尋演算法(Depth-First-Search)

以Python實作

參考

二元搜尋樹 Binary Search Tree

主要的優點就是時間複雜度能優化至O(logN)

  • 每個節點最多有兩個子節點
  • 子節點有左右之分
  • 左子樹的節點小於根節點、右子樹的節點大於根節點
  • 節點值不重複
Average case Worst case
insert O(logN) O(N)
delete O(logN) O(N)
search O(logN) O(N)

以Python實作insert, remove, search,執行結果請參考gist

BST現實中的應用

  • OS file system
  • 機器學習:決策樹

平衡二元搜尋樹 Balancing Binary Search Tree, AVL Tree

  • 能保證O(logN)的時間複雜度
  • 每次insert, delete都要檢查平衡,非平衡需要額外做rotation
  • 判斷是否平衡:
    • 左子樹高度 - 右子樹高度 > 1: rotate to right
    • 左子樹高度 - 右子樹高度 < -1: rotate to left
    • image
Average case Worst case
insert O(logN) O(logN)
delete O(logN) O(logN)
search O(logN) O(logN)

不適合用在排序,時間複雜度為O(N*logN)

  • 插入n個:O(N*logN)
  • in-order迭代:O(N)

繼承上面BST繼續往下實作,有bug請協助指正,執行結果請參考gist

  • 任一節點設定完left或right,更新該節點height
  • 每個insert的call stack檢查檢查節點是否平衡,不平衡則rotate

紅黑樹 Red-Black Tree

  • 相較於AVL樹,紅黑樹犧牲了部分平衡性換取插入/刪除操作時更少的翻轉操作,整體效能較佳(插入、刪除快)
  • 不像AVL樹的節點屬性用height來判斷是否須翻轉,而是用紅色/黑色來判斷
    • 根節點、末端節點(NULL)是黑色
    • 紅色節點的父節點和子節點是黑色
    • 每條路徑上黑色節點的數量相同
    • 每個新節點預設是紅色,若違反以上規則:
    • 翻轉,或
    • 更新節點顏色

image

Average case Worst case
insert O(logN) O(logN)
delete O(logN) O(logN)
search O(logN) O(logN)

github上用python實作的範例:Red-Black-Tree

優先權佇列 Priority Queue

  • 相較於Stack或Queue,對資料項目的取出順序是以權重(priority)來決定
  • 常用heap來實作

二元堆積 Binary Heap

  • 是一種二元樹資料結構,通常透過一維陣列(one dimension array)
  • 根據排序行為分成minmax
    • max heap: 父節點的值(value)或權重(key)大於子節點
    • min heap: 父節點的值(value)或權重(key)小於子節點
  • 必須是完全(compelete)二元樹或近似完全二元樹

註:

  • heap資料結構跟heap memory沒有關聯
  • 優勢在於取得最大權重或最小權重項目(root),時間複雜度為O(1)
time complexity
insert O(N) + O(logN) reconsturct times
delete O(N) + O(logN) reconsturct times

應用

  • 堆積排序法(Heap Sort)
  • 普林演算法(Prim’s Algorithm)
  • 戴克斯特拉演算法(Dijkstra’s Algorithm)

堆積排序 Heapsort

  • 是一種比較排序法(Comparision Sort)
  • 主要優勢在於能確保O(NlogN)的時間複雜度
  • 屬於原地演算法(in-place algorithm),缺點是每次排序都須重建heap——增加O(N)時間複雜度
  • 在一維陣列起始位置為0的indexing:

image

操作可參考這篇文章:Comparison Sort: Heap Sort(堆積排序法)

用Python實作Max Binary Heap,請參考gist

python build-in heapq

關聯陣列/對映/字典 Associative Array/ Map/ Dictionary

  • 鍵、值的配對(key-value)
  • 相較於樹狀資料結構,劣勢在於排序困難
  • 主要操作:
    • 新增、刪除、修改值
    • 搜尋已知的鍵

image

hash function

  • division method: modulo operator

h(x) = n % m

n: number of keys, m: number of buckets

Collision

當多個key存取同一個bucket(slot),解決collision會導致時間複雜度提高

解法:

  • chaining: 在同一個slot用linked list存放多個關聯
  • open addressing: 分配另一個空的slot
    • linear probing: 線性探測
    • quadratic probing: 二次方探測,如1, 2, 4, 8…
    • rehashing

Second Round皆有詳盡解說:

Dynamic resizing

load factor(佔用率): n / m

  • load factor會影響到存取的效能,因此須要根據使用率動態變更陣列大小;
  • 舉例來說,Java觸發resize的時機點大約是佔用超過75%時、Python則約是66%

應用

  • 資料庫
  • Network Routing
  • Rabin-Karp演算法
  • Hashing廣泛用於資料加密

參考:

  • http://www.globalsoftwaresupport.com/use-prime-numbers-hash-functions/
  • http://alrightchiu.github.io/SecondRound/hash-tableintrojian-jie.html#collision

以Python實作,請參考gist

Average case Worst case
insert O(1) O(N)
delete O(1) O(N)
search O(1) O(N)

三元搜尋樹 Ternary Search Tree, TST

  • 相較其他樹狀資料結構而言,佔用記憶體空間較小
  • 只儲存string,不存NULL或其他物件
  • 父節點可以有3個子節點:left(less)middle(equal)right(greater)
  • 可以同時用來當作hashmap使用,也可以做排序
  • 效能上比hashmap更佳,在解析key時是漸進式的(如cat若root沒有c就不用繼續找了)

image

應用

  • autocompelete
  • 拼字檢查
  • 最近鄰居搜尋(Near-neighbor)
  • WWW package routing
  • 最長前綴匹配(perfix matching)
  • Google Search

以Python實作,請參考gist

互斥集 Disjoint sets / union-find data structure

  • 一堆沒有交集的集合,如10個學生分成4組
  • 主要操作: unionfindmakeSet
  • 通常以linked list或tree來實作
  • 訪問disjoint set中的任何節點都回傳同一個root value

set在union過程中會遇到不平衡的問題,有兩種最佳化方法:

  1. union by rank: 讓小的樹接到較大的樹
  2. path compression: 訪問節點時調整樹的結構,直接與root連結

應用

  • Kruskal: 檢查圖中是否有cycle

以Python實作,輸出請參考gist

發表留言

Docker基礎介紹與實戰

TOC

image

Docker簡介

  • Docker是2013年由DotCloud開發的開源專案,因為軟體的成功,公司之後也改名為Docker.Inc
  • 其實Docker的前身是象龜(@gordonTheTurtle),之後改成鯨魚

Docker的重要性

  • 容器將會是未來最有影響力的基礎架構
  • 不管你是開發、維運、系統管理、部屬都會須要學一下
  • 成長最快的雲端技術
  • Infrastructure as code

Docker的優點

  • 過去底層的基礎架構都是為sysops或sysadmins設計的,但Docker的解決方案考慮了developer的使用情境
  • 就是快:develop faster, build faster, test faster, deploy faster, update faster, recover faster
  • 使用容器來減少不同應用程式、不同系統、不同相依性的複雜度
  • 大部分現存的軟體開發得少維護得多,Docker可以減輕我們維護的難度,增加開發的時間

相關工具

學會了Docker你可能需要多了解其他工具

安裝前要注意的

在不同環境下的安裝:

  • Linux: 直接安裝,不過有分版本
  • Windows: Win10不直接支援,需要透過virtual machine來安裝,在安裝的流程中會設定到;因為Windows Containers的出現,新的Windows Server 2016之後會開始直接支援Docker,不用再透過Linux Container
  • Mac: 也是要透過vm,據說不要用brew來裝
  • Cloud: Docker for GCP/AWS/Azure

看更多有關windows container:

Stable vs Edge(beta)

開源版本的Edge每個月會更新一次,Stable大概四個月更新一次,付費的企業版本會有更穩定的支援

安裝

Windows 10 Pro / Enterprise

  • 使用Pro跟Enterprice的用戶算是比較吃香,會有比較好的體驗
  • 請在此下載安裝Docker for Windows
  • 命令界面(CLI)會建議使用PowerShell
  • 如果你的系統已經有在使用VirtualBox或VMware,Hyper-V啟用可能會發生資源互搶的問題

Windows 7, 8, or 10 Home Edition

  • Win7, 8與Pro/Enterprise的主要差別是Hyper-V過舊不支援,Win10 Home則是沒有Hyper-V,因此要額外安裝Docker Toolbox,然後透過VirtualBox安裝Linux VM(Linux Container),也就是 docker-machine 使用網路位址轉換(NAT)存取網路
  • 需要把Toolbox的 http://localhost 改成 http://192.168.99.100

Mac

  • OSX Yosemite 10.10.3以下版本的需要裝Toolbox,以上版本安裝Docker for Mac

Linux

  • 不要用系統預設的packages直接下指令安裝,例如 apt install docker.io,你可能會裝到過舊的版本
  • 可以經由Docker automated script來安裝相依的程式,例如 curl -ssl https://get.docker.com/ | sh
  • 或者先看過官網上的要求來手動下載安裝檔

Docker會動用到系統核心的功能,需要root權限來操作 你可以將使用者加入Docker group,註:有些版本的linux如Red Hat, Fedora沒有此選項,每個指令都要透過 sudo

Docker Machine & Docker Compose

Windows和Mac會自動幫你安裝好,Linux系統則要自己安裝這兩個項目

Docker compose

使用官方文件提供的指令可能會安裝到非最新的Docker compose版本,你可以到github/docker/compose去安裝最新的版本

版本格式

現今版本的格式為 YY.MM,如 18.06.0 就是2018年6月出的版本的第一個release

其他選項

你可以試試Play with Docker,無須安裝任何環境就可以透過瀏覽器體驗Docker的強大,可以參考以下文章:

指令格式

舊的指令格式為:docker <command> (options),例如 docker run,而新的Docker指令的格式已改成:

例如 docker run 現在要寫成 docker container run,不過舊的指令格式目前還能使用

安裝完成你可以試著執行以下指令:

  • docker: 檢視所有管理選項
  • docker version: 確認你可以跟docker engine溝通,和檢查你的版本號
  • docker info: 檢視docker engine的各項設定值
  • docker image ls: 檢視本機所有映像檔
  • docker pull <-- IMAGE -->: 取得映像檔

容器(Containers)

什麼是容器

容器是基於特定映像檔所創造出來的程序

映像檔(image)和容器(container)的差別

  • 映像檔是我們想執行的應用程式的模板,一個映像檔可以包含一個完整的作業系統環境,裡面安裝了需要的應用程式,同一個映像檔可以建立多個容器
  • 容器是從映像檔建立的執行實例,可以被啟動、開始、停止、刪除
  • 大部分的映像檔都可以從Docker Hub下載並取用

基本指令

  1. 從Docker Hub下載nginx映像檔
  2. 建立一個映像檔實例(容器),命名為webhost1並執行
  3. Port Forwarding: 啟用主機的80 port並將容器內部使用的80 port映射到主機上

註:

  • nginx server預設的對外端口是80 port
  • 你可以更改要映射到主機的哪個port,例如 8888:80,然後用 localhost:8888 訪問

加入detach參數只在背景執行,並回傳容器的id

顯示目前所有容器

關閉正在運行的容器

顯示webhost2的log

檢視webhost2的程序,或檢查是否有正在運行的webhost2

強制關閉運行中的容器

以上這些指令發生了什麼事

docker container run

  1. 尋找特定的映像檔快取,找不到該映像檔的話會從遠端倉庫(remote image repo)尋找(預設是Docker Hub,若沒提供映像檔版本將下載最新的版本)
  2. 新增映像檔實例(容器)
  3. Docker引擎會給這個容器一個實體IP(virtual ip)
  4. 啟用host的特定埠號將容器的特定埠號映射到host
  5. 以映像檔的Dockerfile CMD來執行容器

容器(container)和虛擬機(virtual machine)的差別

很多介紹容器的文章把容器拿來和vm比較,雖然它們相似的地方很多,但事實上它們是完全不同的概念,因為:

  • 容器只是程序(processes)
  • 容器可用的資源是受限的
  • 容器關閉=程序停止

舉個例子來說:

檢視此容器是否正在運行

檢視所有運行中的docker容器,註:docker ps 會對象是host,如果系統是mac或windows,需要先連到docker vm

檢視系統上所有程序,可以清楚的看到容器是一個系統上正在執行的程序

練習:開啟多個容器

  1. 執行一個nginx實例,在背景執行且聽80port
  2. 執行一個httpd(apche)實例,在背景執行且聽8080port
  3. 執行一個mysql實例,在背景執行、密碼設定為自動產生,聽3306port

檢視這三個容器的狀態

檢視mysql密碼

基本指令:監控執行中的容器

檢視容器的設定(metadata),會回傳json陣列

檢視所有容器的即時狀態(live performance)

基本指令:在容器中使用終端機

  • run -t: Allocate a pseudo-TTY
  • run -i: Keep STDIN open even if not attached

根據格式 docker container run [OPTIONS] IMAGE [COMMAND] [ARG...],在指令後面可以再帶入 [COMMAND] 及參數 [ARG...],以nginx為例,預設程式(default program)是 nginx,參數是 -g'daemon off;'

加入bash參數來改變預設程式,進入bash shell之後exit,可以看到容器隨之停止:

這邊可以看出預設的程式變成bash:

  • start -a: Attach STDOUT/STDERR and forward signals
  • start -i: Attach container’s STDIN

重啟容器,注意這裡一樣會開啟bash shell因為我們建立容器時就把command改成bash了:

如果容器正在執行中,我們如何透過殼程式操作呢(這很常用,在容器執行時debug或設定參數):

  • exec: 在執行中的容器上執行額外的程序
  • exec -t: Allocate a pseudo-TTY
  • exec -i: Keep STDIN open even if not attached

另外要注意的是,並非所有映像檔都有bash程式,例如Linux的超迷你分支alpine[ 延伸閱讀 ]:

要使用bash要先透過alpine內建的殼程式sh來安裝

Docker背後的網路運作

docker讓你可以建立虛擬網路(virtual network),並將container加到網路內,建立起屬於你自己應用程式的網路拓墣 [來源]

docker daemon運作的時候,會建立三個網路

當你建立容器時,使用的ip跟主機並不同,例如以下例子:我的主機內部ip是 192.168.43.63,而我的nginx容器的內部ip則是 172.17.0.2

它們之間透過docker birdge network的模式(預設),此模式透過一個叫docker0的NAT server來掌管容器的網路連線 image [圖片來源]

透過以下指令可以看出bridge網路裡有哪些容器

新增一個網路(預設是bridge driver),然後建立新的容器

連結容器至多個網路

DNS設置

容器隨著不同設置會改變其狀態,容器間用ip位址來連線是不可靠的,因此我們會需要DNS server

先來看看新網路的DNS能不能運作

同樣在nginx容器安裝ping套件後,測試能不能雙向溝通

註:如果我以上的測試在預設的bridge network做的話會出現錯誤訊息 Name or service not known,原因是預設的bridge網路並沒有內建的DNS server,容器要連線必須手動使用 --link 指令為容器設定連線到bridge網路。建議建立新的網路來省去這一步驟

練習:快速更新Linux分支的CLI套件

  1. 分別檢查容器不同分支的Linux上的curl版本
  2. 分別在centos:7和ubuntu:14.04的容器中開啟終端機

分別取得各版本的映像檔

建立ubuntu映像檔實例,開啟終端機並檢查curl版本

container run --rm: exit之後預期容器被移除

建立centos映像檔實例,開啟終端機並檢查curl版本

練習:輪替式DNS(DNS Round Robin aka poor man’s load balancer)

  1. 建立一個network包含兩個elasticsearch:2的映像檔實例
  2. 將兩個容器的網路拓墣別名(network-alias)都命名為search

透過一個內網centos容器來測試連線

curl -s: Silent mode. Don’t output anything

映像檔(image)

什麼是映像檔?根據官方定義跟我隨翻:

An image is an ordered collections of root filesystem changes and the corresponding execution parameters for use within a container runtime

映像檔 = 檔案系統變動的有序集合 + 執行一個實例時相對應的執行參數

  • 不是作業系統、沒有內核(kernal)、沒有核心模組(kernal module)[ 延伸閱讀 ]
  • 體積非常輕量,小則幾KB(golang static binary),大則幾GB(ubuntu distro + apache + php)

Docker Hub

目前最多人用的映像檔的集散地(image registry),映像檔repo分成 officialnon-official,映像檔會用 tag 做區別(正確來說tag不算版本或分支),舉例來說,官方的nginx repo:

  • 1.15.5, mainline, 1, 1.15, latest (mainline/stretch/Dockerfile)
  • 1.15.5-perl, mainline-perl, 1-perl, 1.15-perl, perl (mainline/stretch-perl/Dockerfile)
  • 1.15.5-alpine, mainline-alpine, 1-alpine, 1.15-alpine, alpine (mainline/alpine/Dockerfile)

分別pull下來,可以看出同一個映像檔(相同image id)可以有多個tags

Docker Hub的操作跟Github很類似,可以fork其他repo或是上傳新的repo,你所需要的資訊也都能在detail page或github上找到。

  • 映像檔可以從Github或Bitbucket的repo來建置,從首頁點選 Create Automated Build 連結帳戶即可
    • 可以選擇你的映像檔要從哪個分支來build
    • 對於dockerfile的 FROM 指令,Docker Hub可以設定Repository Links,設定連結後,來源映像檔如果有偵測到更新,你的映像檔就會跟著rebuild
    • 自定義Rebuild的Trigger

Union檔案系統(Union file system) && 映像檔的資料層(Image layers)

映像檔的形成並非一個大的資料區塊,例如當我pull nginx:alpine時,可以看到有些資料層我已經有了

透過 image history 來檢視映像檔的資料更動紀錄:

註:IMAGE ID為 <missing> 只是為了區別——這些資料層不完整代表這個映像檔,只是被這個映像檔所用

所有映像檔都是繼承於特定基礎映像檔(blank layer, scratch),再往上繼承堆疊,不同映像檔之間可以共享基礎的檔案系統層,提升儲存效率 [ 延伸閱讀 ]

image [ 圖片來源 ]

image [ 圖片來源 ]

透過 image inspect 來檢視映像檔的metadata,可以看到這個映像檔開了哪些port、有哪些環境變數以及建立的時候會執行的指令等

fork一份映像檔到自己的repo

註:

  • 在遠端機器使用 docker login 的時候,操作完要登出才會移除機器上儲存的密碼
  • 欲上傳private映像檔要先建立一個私人的repo

Dockerfile基本指令

範例1

  • FROM: 開頭一定要加的,指定一個 Base Image 來初始化,通常是取用較輕量的分支如alpine
  • ENV: 設定環境變數
  • RUN: 每行指令都會往上建築新的 layer(new layer on top),上面的範例用 && 來連結每一行指令是常見的作法
  • EXPOSE: 容器沒有任何預設開啟的TCP/UDP埠號,而加了也不代表這些埠號會自動打開,要開啟埠號還是要透過 container run -p
  • CMD: 容器執行或停止的時候都會執行的指令 註:docker能自動幫我們處理logging,上面的範例將log送到stdout跟stderr,讓docker可以捕捉到這些log然後做後續處理

在dockerfile所在的目錄下build image

稍微更改一下dockerfile再安裝一次,可以看出union file system怎麼運作的,dockfile頂端指令會動到的資料幾乎沒什麼變動:Using cache,而越接近後頭的指令,會有較多的變動

範例2

  • 如果你能從更末端的映像檔來build,例如 FROM 官方的nginx再做一些客製化,在維護dockerfiles時就會更加容易。如範例A到範例B
  • WORKDIR: 意同 RUN cd /some/path,不過使用 WORKDIR 會更好
  • COPY: 這個範例用本地的index.html複寫原本nginx的index.html
  • 這個範例並沒有改寫 CMD,這個指令會繼承 nginx:latest 的command

建立一個容器,訪問主頁面時預期會看到客製後的index.html

練習:建立Dockerfile(alpine + node.js + tini)

發布到Docker Hub

持久化數據(Persisting Data)

容器的設計理念有兩個特性:

  1. immutable intrastucture: 當你需要更改設定,皆是透過重新建立新的容器
  2. ephemeral:無狀態,代表容器可以被關閉、銷毀或取代

延伸閱讀: Pets vs Cattle Analogy

容器的特性帶來可靠性(iability)和一致性(consistency),不過相對地帶來一個問題,即如何維持持久化數據(如資料庫),針對這種關注點分離(Seperation of concerns, SOC)的架構設計問題,Docker提供了幾個方案:

  1. Volumes: 由Docker管理,存儲在宿主機的某個地方(在linux上是/var/lib/docker/volumes/)。非Docker應用程序不能改動這一位置的數據。Volumes是Docker最好的數據持久化方法
  2. Bind mounts: 數據可以存放在宿主機的任何地方。數據甚至可以是重要的系統文件或目錄。非Docker應用程序可以改變這些數據;適用於local 開發測試
  3. tmpfs mounts: 數據只存儲在宿主機的內存中,不會寫入到宿主機的文件系統 [ 來源 ]

image

Volumes

先來看一下mysql:8 Dockerfile的 VOLUME 指令

意即當mysql的容器建立的時候,docker會在本機上新增一個 volume location 然後跟容器裡的數據目錄互通,這兩個路徑指向host同一個位址,資料只有靠手動方式才能移除,不會隨著容器被移除而消失

建立一個mysql容器然後看metadata

可以看到data儲存在主機 /var/lib/docker/volumes/c7eaf7ddfa3ad5d90abbe0372d628f97ddcc2384dae45a35d60c74bfdac37416/_data 的位置

而這在使用上會比較不友善:沒辦法從volume看出哪個容器是連結到自身

這裡稍微改善的方式是用參數 -v 為volume命名(以專案來命名之類的)

source的path也變得比較乾淨易讀

另外Volume也可以是匿名的(anonymous volume),會分配一個隨機的名字,在同一個主機中不會重覆[ 官方文件 ]

Bind Mounting

  • host優先於container
  • 不能在Dockerfile裡使用,只能透過 container run
  • 通常是用 -v 指令,或 --mount,格式為 /path/host:/path/container,如

範例

新增一個nginx容器,把 /home/ssivart/桌面目錄mount至容器的 /usr/share/nginx/html

接著我在 /home/ssivart/桌面新增一個index.html,內容為 <h1>Hello World! Bind Mount</h1>

預期在localhost:80會看到我更改過的首頁內容

-v--mount 的差別[ 文件 ]

現在這兩個指令差別僅在於如果host上目錄不存在,使用 -v 會幫你建立新的目錄,使用 --mount 會顯示錯誤

練習:postgres版本更新用Volumes保持數據

練習:使用Bind Mount架設ruby + jekyll[ 來源 ]

Docker Compose[ 官方文件 ]

  • 應用程式常常需要結合多個容器如SQL、proxy、網頁和後端排程等,docker compose便是用來設置容器之間的關係
  • 一鍵完成
  • 可以透過Docker Swarm 1.13以上版本部屬compose file

Docker compose包含兩個部份:

  1. docker-compose.yml: YAML格式的文件來設定容器、網路、Volumes的hierarchy
    • 有區分版本,如1, 2, 2.1
    • -f 來讀取特定檔案,預設讀取的檔名是 docker-compose.yml
  2. docker-compose: 命令列(CLI)工具用來測試compose file

以下是compose file範例:

一個簡單的proxy network設定如下

docker compose CLI

  • 請參考上方提到的安裝流程
  • 僅用於開發測試端,非正式部屬使用

常用指令

  • docker-compose up: 設定volumes/networks,執行所有容器,使用 -d 讓程序於背景執行
  • docker-compose down: 停止並移除所有容器,常用 -v 來移除所有volumes/networks

練習: drupal + postgres架站

使用compose來建置(build)客製化映像檔

  • 使用 docker-compose up,如果找不到該映像檔的cache,會在當下建置
  • re-build使用 docker-compose up --builddocker-compose build

先來看以下compose範例:

當proxy容器執行時,會先在cache找nginx-custom這個映像檔,找不到會build,這邊示範了在 context 提供的目錄下用 dockerfile 來建置映像檔

執行完 down,docker並不會主動移除客製化映像檔,針對要移除上面範例經過命名的映像檔,down 指令加上 --rmi all 參數,如果是不提供映像檔名稱,docker會以 <bulid directory>_<container name> 的規則來命名映像檔,可以透過 --rmi local 移除

composefile配置

這個部份官方還在持續更新 往後針對部署端的compose還會有許多變動(swarm、stack、secrets…),之後會持續更新

docker-compose會自動辨識.override.yml 的檔案,.test.yml.prod.yml 或其他自訂的compose file則需要手動透過指令 -f 來操作

本地開發端:docker-compose.yml + docker-compose.override.yml

CI測試:docker-compose.yml + docker-compose.test.yml

部署通常會用 config 輸出成一個完整的compose file:docker-compose.yml + docker-compose.prod.yml

Swarm Mode

  • Swarm用來做(叢集)架構管理
  • Swarm內建於Docker
  • 多個Docker host組成1個Swarm mode
  • Swarm程式獨立於Docker,在Docker環境下透過swarmkit運行(須先啟用)

Node

Swarm中的Docker的實例(Docker host)

image

  • Worker:
    • 負責容器的執行
  • Manager:
    • 在Swarm中透過Raft Database的設定進行協調與同步
    • 負責管理Worker與協調容器的部署工作
    • 也可以當Worker,Manager可以想像成有Swarm控制權限的Worker

兩個角色可以互換,node之間透過雙向TLS(mutual Transport Layer Security, 前身是SSL)協定溝通

image

Service and Task

  • Task: Container + Command(怎麼run這個容器)
  • Service: Task A + Task B + Task C(任務的堆疊),基於 docker run 的再封裝

Swarm會確保services持續運作

在新的處事方式上,服務器被編好號,就像牛在牛群中。比如,www001到www100。當一個服務器宕機了,它將會被取出替換上線 —— Randy Bias

image

Swarm運作

image [ 圖片來源 ]

基本指令

docker swarm init 可以新增一個swarm,其中完成這些動作[ 文件 ]

  1. 初始公開金鑰基礎架構(public key infrastructure, PKI)
    • Docker先扮演第一個Manager node(root manager)
    • 產生一組root certificate authority(.ca)
    • 產生token: worker token + manager token
    • 其他node可以用這組token加入(join)swarm
  2. 初始Raft database
    • 儲存憑證
    • 在control plane讓Manager之間共享log,透過TLS
    • 儲存config data[ 文件 ]

初始後可以看到第一個manager node

建立一個service,指定task的容器執行的指令及參數(docker run)

檢視service的task,可以看到一個執行中的容器

更新service的參數,提高task數量(replicas)來sacle up

手動停止其中一個容器,service會再add一個task到service queue取代之,確保同時會有3個正常運作的task

要終止task必須移除整個service,同時會把當中所有的程序清理掉

更新services

指令範例

service更新其中的tasks都會重建,task建立時docker會挑負載較小的node做較多分配,所以對service做 force update 可以平衡node之間的負載

CLI更新

service createservice update 在不同版本上的參數變更:

  1. 17.05前:必須都要透過 service lsservice ps 來檢查是否正常執行
  2. 17.05後:新增了 --detach 參數,預設為true
  3. 17.10後:--detach 參數,預設改為false 結論是17.12版本後要透過shell scripts或automation來建立service的話,記得設定 --detach=true

使用GCP Compute Engine Instance Group來試作多節點Swarm

worker node沒有swarm的控制權

提昇host的worker成manager,node狀態變成Reachable

Overley Network

跨Node間的網路拓樸,基於Routing Mesh

psql在node1,drupal在node2,node2可以透過dns name:psql來存取node1的資料庫,用80 port連到node1也能看到drupal的歡迎頁面

Routing Mesh

  • 為service底下的task提供路由(routes ingress packets)
    • container-to-container use Virtual IP
    • external traffic incomming to publish ports(all nodes listen)
  • 為services提供負載平衡
    • stateless load balancer,如果需要指向特定container的話,須另外設定(如cookie、session)
    • 使用linux既有的IPVS(IP Virtual Server)實現load balancing
    • load balancer表現於TCP層的(OSI第3層),針對一個swarm一個對外port的web架構,還會需要Nginx、HAProxy來做DNS層(第4層)的load balancer
  1. 無論訪問網路中的哪個節點,即使該節點上沒有運行該service的副本,最終都能訪問到該service 舉例來說,如果後端資料庫有3個副本,當前端web server要取資料時,並非直接訪問某個資料庫副本的ip,而是透過swarm為所有service搭建的Virtual IP(VIP)
  2. 外部流量導向所有節點共同監聽的public port

image

image

練習:voting app[ dockersamples ]

image

首先先建立前後端的網路,跨node要用overlay模式

Stacks

  • dockerfile版本3以上
  • 適用於部署端的compose(services + volumes + overlay networks + secrets…)
  • composefile指令跟local端的差異:開發端忽略 deploy 指令,部署端忽略 build
  • 部署不透過docker-compose
  • 針對已經存在的service,再部署會update這些service

上述的範例只要透過一個Composefile就能完成所有部署

swarm的可視化工具visualizer

image

Secrets

  • 儲存:
    • 使用者帳密
    • TLS憑證、金鑰
    • SSH金鑰
    • 自訂設定檔
  • 支援動態字串或二進位的內容
  • 儲存在Raft database,只存在於各個manager node的硬碟空間
  • secrets產生後會先存在swarm再分配到特定service,只有特定的container能訪問
  • 乍看之下是個實體檔案,但實際上是ramfs files system透過記憶體儲存
  • secrets只適用於swarm,而swarm只適用於部署端(production),但docker讓docker-compose也能使用,僅提供開發測試,不具真的功能(fake secure)

secrets無法透過指令檢視內容(廢話),容器要存取secret得先經過指定

但這樣還少了怎麼配置這些secret,docker提供一個方便的查找方式,前提是映像檔要有這些變數

終端機進容器檢視密碼

針對已經建立的service可以透過 update --secret-rmupdate --secret-add 來更新secret,但這樣做會導致整個service重新部署

secrets透過stack部署

  • dockerfile版本3.1以上

透過實體檔案儲存密碼的方式是有風險的,記得在部署完成後移除這些檔案

開發端測試secrets

  • docker-compose CLI版本11以上適用
  • 開發端是使用bind mount的方式把secrets mount到本機目錄,只是用於開發並無secret實際功能
  • 不適用內含external用法,如果部署是用 external:true,可以另外維護一個開發用的compose file,secret的部份改用file來寫入

Healthchecks

  • 回傳0(OK)或1(Error)
  • 支援Dockerfile、Compose、docker run 及swarm services
  • 容器有三種健康狀態: starting, healthy, unhealthy

container

services

Docker Registry

push之後映像檔的實體檔案儲存在/var/lib/registry/

Registry in Swarm mode

  • 主要問題是解決nodes之間要訪問同一份映像檔
  • 如果只在其中一個manager node build image,其他node是沒辦法取得該image
  • 解法:
    • 使用Docker hub、AWS、Quay來儲存管理映像檔
    • 借助Routing Mesh,所有node都能藉由127.0.0.1:5000訪問

以下示範如何使用Routing Mesh建立registry

將映像檔push到registry後,所有nodes都能從127.0.0.1:5000取得映像檔

發表留言